Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@fusede
Copy link
Collaborator

@fusede fusede commented Jul 8, 2025

Summary by CodeRabbit

  • New Features

    • Full BIP-322 support added: message signing/verification, address parsing/validation, transaction builders, and workspace integration for BIP-322 payloads.
  • Documentation

    • Comprehensive BIP-322 README with usage, supported formats, limitations, and testing notes.
  • Tests

    • Large NEAR test suite, new test signer flow, many wallet & reference vectors, and Python validation tools.
  • Chores

    • Workspace configuration updated and .gitignore expanded.
  • New Utilities

    • Added hashing utilities (Hash160, double-hash and tagged-hash support).

@coderabbitai
Copy link

coderabbitai bot commented Jul 8, 2025

Walkthrough

Add a new workspace crate defuse-bip322 implementing BIP-322 message verification (address parsing, minimal Bitcoin types, tx builders, hashing, signature parsing/recovery, verification), wire it into core payloads and tests, extend near-utils digest API, update manifests and .gitignore, and add Python validation scripts and test stubs.

Changes

Cohort / File(s) Summary
Repo config
/.gitignore
Add ignore patterns: /.idea, .claude/, CLAUDE.md.
Workspace manifests
/Cargo.toml, core/Cargo.toml, tests/Cargo.toml
Add bip322 as workspace member and workspace dependency path; add defuse-bip322/abi to core abi features; add dev-deps for tests; bump near-contract-standards.
New crate: bip322 (manifest & docs)
bip322/Cargo.toml, bip322/README.md
New crate manifest (workspace wiring, deps, features including abi) and README describing scope, design, tests, and limitations.
Library root & exports
bip322/src/lib.rs
New public modules and re-exports; define SignedBip322Payload and impls for Payload and SignedPayload.
Bitcoin minimal model & encoding
bip322/src/bitcoin_minimal.rs
Add minimal Bitcoin types (Address enum, ScriptBuf, Txid, OutPoint, TxIn/TxOut, Transaction), parsing (Base58Check/Bech32), opcodes, Encodable trait and consensus encoding (witness/legacy), and sighash helper functions.
Address errors
bip322/src/error.rs
Add AddressError enum with Display and Error impls for address parsing failures.
BIP-322 hashing
bip322/src/hashing.rs
Add Bip322MessageHasher with tagged message hashing and address-aware sighash selection (legacy vs segwit v0) returning near_crypto hashes.
Signature handling
bip322/src/signature.rs
Add Bip322Signature (Compact/Full), Bip322Error, base64 parsing, varint/witness helpers, compact recovery via env::ecrecover, full-witness parsing, public-key extraction and message-hash selection.
Transaction builders
bip322/src/transaction.rs
Add create_to_spend, create_to_sign, and compute_tx_id helpers (to_spend embeds message hash; to_sign references to_spend TXID).
Verification utilities
bip322/src/verification.rs
Add pubkey-vs-address validation functions for P2PKH/P2WPKH/P2SH/P2WSH and nested P2SH-P2WPKH handling (supports compressed/uncompressed paths).
Tests (Rust)
bip322/src/tests.rs
Add comprehensive NEAR-SDK test suite and many test vectors (official vectors, wallet vectors, hashing/tx/signature tests).
Python validation scripts
bip322/validate_unisat_vector.py, bip322/validate_unisat_comprehensive.py
Add diagnostic scripts for UniSat vector validation (bech32 parsing, message hashing, pubkey recovery attempts, verbose diagnostics).
Core payload integration
core/src/payload/mod.rs, core/src/payload/bip322.rs, core/src/payload/multi.rs
Add bip322 module; add SignedBip322Payload ExtractDefusePayload impl skeleton; add MultiPayload::Bip322 variant and dispatch arms for hash, verify, and extract_defuse_payload.
Tests: signing stubs & flow
tests/src/utils/crypto.rs, tests/src/tests/defuse/mod.rs
Add Signer::sign_bip322 test stub producing a dummy SignedBip322Payload; add SigningStandard::Bip322 and wire test signing flow in test harness.
near-utils digest expansion
near-utils/src/digest.rs
Add Hash160 (RIPEMD160(SHA256)), generic Double<D> wrapper and DoubleSha256 alias, TaggedDigest trait, and tests exercising these digests.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Core as core::MultiPayload
  participant B322 as defuse_bip322::SignedBip322Payload
  participant Hasher as Bip322MessageHasher
  participant Tx as bip322::transaction
  participant Sig as Bip322Signature
  participant Ver as bip322::verification

  Client->>Core: verify()
  Core->>B322: delegate verify()
  B322->>Hasher: compute_bip322_message_hash(message)
  Hasher-->>B322: message_hash
  B322->>Tx: create_to_spend(address, message_hash)
  Tx-->>B322: to_spend
  B322->>Tx: create_to_sign(to_spend)
  Tx-->>B322: to_sign
  B322->>Sig: extract_public_key(&message_hash, &address)
  Sig->>Ver: validate_pubkey_matches_address(pubkey, address)
  Ver-->>Sig: bool
  Sig-->>B322: Option<PublicKey>
  B322-->>Core: verification result
  Core-->>Client: result
Loading
sequenceDiagram
  participant App
  participant Core as core::MultiPayload
  App->>Core: hash()
  alt variant == Bip322
    Core-->>App: SignedBip322Payload.hash()
  else
    Core-->>App: other variant.hash()
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • mitinarseny

Poem

I hop through hashes, varints, and test arrays,
Bech32 and sigs in my jittery gaze.
I nibble bugs, stitch txs with care,
Pocket full of vectors, carrots to share.
Hooray — BIP-322, a rabbit's repair! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6cde859 and cfa1eb8.

📒 Files selected for processing (1)
  • bip322/src/tests.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • bip322/src/tests.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Security Audit - report
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/bip322

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@siydefuse siydefuse marked this pull request as ready for review July 28, 2025 10:08
Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (6)
core/Cargo.toml (1)

10-10: New crate wired into the core crate
Non-optional defuse-bip322 means every downstream consumer now builds this crate. If build size or compile time is a concern, consider gating it behind a feature (similar to other *_connect crates). Otherwise LGTM.

bip340/src/lib.rs (1)

1-4: Crate root minimal but complete
Re-exporting all items from the double and tagged modules is fine for a small utility crate. If the public surface grows, consider explicit exports to avoid namespace pollution.

core/src/payload/multi.rs (1)

56-57: Add documentation for BIP-322 variant.

The TODO comment indicates missing documentation for the new BIP-322 variant. This should be completed to maintain consistency with other well-documented variants.

-    // TODO: docs
-    Bip322(SignedBip322Payload),
+    /// BIP-322: The standard for Bitcoin generic message signing.
+    /// For more details, refer to [BIP-322](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki).
+    Bip322(SignedBip322Payload),

Would you like me to open an issue to track this documentation task?

bip322/src/der.rs (2)

49-53: Add validation for canonical DER length encoding.

The current implementation doesn't validate against non-canonical length encodings. According to DER rules, the length should be encoded in the shortest possible form. For example, a length of 127 should use short form (0x7F), not long form (0x81 0x7F).

Consider adding validation to reject non-canonical encodings:

 let mut length = 0usize;
 for &byte in bytes.iter().take(len_bytes + 1).skip(1) {
     length = (length << 8) | usize::from(byte);
 }
+
+// Validate canonical encoding - no leading zeros except for single zero byte
+if len_bytes > 1 && bytes[1] == 0 {
+    return None; // Non-canonical: leading zero
+}
+
+// Validate minimal encoding - could have used short form
+if len_bytes == 1 && length <= 127 {
+    return None; // Non-canonical: should use short form
+}

245-273: Consider adding test cases for edge conditions.

The test suite is comprehensive but could benefit from additional edge cases:

#[test]
fn test_parse_der_ecdsa_signature_trailing_data() {
    // Signature with extra bytes after valid content
    let invalid_der = vec![
        0x30, 0x06, // SEQUENCE, length 6  
        0x02, 0x01, 0x01, // INTEGER, length 1, value 0x01 (R)
        0x02, 0x01, 0x02, // INTEGER, length 1, value 0x02 (S)
        0xFF, // Extra byte
    ];
    assert_eq!(parse_der_ecdsa_signature(&invalid_der), None);
}

#[test]
fn test_parse_der_length_non_canonical() {
    // Length 127 encoded in long form (should use short form)
    let non_canonical = vec![0x81, 0x7F];
    // Currently passes but should fail with canonical validation
    assert_eq!(parse_der_length(&non_canonical), Some((127, 2)));
}
bip322/README.md (1)

139-154: Example code references undefined function.

The example uses witness_from_signature_data() which is not defined in the codebase. Consider providing a complete example or clearly marking this as pseudo-code.

 // Create a BIP-322 payload
+// Note: This is a simplified example. In practice, you would parse the witness
+// data from the actual BIP-322 signature format.
 let payload = SignedBip322Payload {
     address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l".parse()?,
     message: "Hello Bitcoin!".to_string(),
-    signature: witness_from_signature_data(signature_bytes),
+    signature: Witness::from_stack(vec![signature_bytes, public_key_bytes]),
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11fe297 and 61f54f3.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .gitignore (1 hunks)
  • Cargo.toml (2 hunks)
  • bip322/Cargo.toml (1 hunks)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/der.rs (1 hunks)
  • bip322/src/error.rs (1 hunks)
  • bip322/tests/integration_test.rs (1 hunks)
  • bip340/Cargo.toml (1 hunks)
  • bip340/src/double.rs (1 hunks)
  • bip340/src/lib.rs (1 hunks)
  • bip340/src/tagged.rs (1 hunks)
  • core/Cargo.toml (2 hunks)
  • core/src/payload/bip322.rs (1 hunks)
  • core/src/payload/mod.rs (1 hunks)
  • core/src/payload/multi.rs (5 hunks)
  • tests/Cargo.toml (1 hunks)
  • tests/src/tests/defuse/mod.rs (2 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
bip340/src/lib.rs (1)
bip340/src/tagged.rs (2)
  • tagged (5-5)
  • tagged (9-12)
core/src/payload/bip322.rs (2)
core/src/payload/mod.rs (2)
  • extract_defuse_payload (51-51)
  • extract_defuse_payload (58-60)
core/src/payload/multi.rs (1)
  • extract_defuse_payload (105-116)
bip322/src/error.rs (1)
bip322/src/bitcoin_minimal.rs (1)
  • fmt (594-602)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Build
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (32)
.gitignore (1)

3-3: Good addition to ignore IDE metadata
Adding .idea helps keep JetBrains project files out of version control. No concerns here.

tests/Cargo.toml (1)

13-13: Dependency name looks correct, but double-check feature flags
defuse-bip322 is added with default features. If the crate exposes heavy optional features (e.g. crypto, abi) that you don't need in tests, consider default-features = false to trim build time.

core/src/payload/mod.rs (1)

1-1: Module registered—verify public re-exports are in place
pub mod bip322; compiles only if core/src/payload/bip322.rs (or a mod.rs subdir) exists and re-exports the necessary symbols. Just a reminder to ensure the file is part of the commit.

core/Cargo.toml (1)

40-40: Consistent ABI feature wiring
"defuse-bip322/abi" correctly added to the abi feature set. All good.

Cargo.toml (1)

6-7: LGTM! Workspace configuration is properly structured.

The new bip322 and bip340 workspace members are correctly added with consistent naming and proper path references in the dependencies section.

Also applies to: 45-46

tests/src/tests/defuse/mod.rs (2)

151-151: LGTM! New signing standard variant properly added.

The Bip322 variant is correctly added to the SigningStandard enum.


129-140: sign_bip322 Implementation Confirmed

I located the sign_bip322 implementation in tests/src/utils/crypto.rs (around lines 71–74). It returns a dummy SignedBip322Payload for testing—consistent with how other signing methods are stubbed in tests. No further changes are needed.

bip340/Cargo.toml (1)

1-18: LGTM! Well-structured crate manifest.

The manifest properly uses workspace-level configuration and includes appropriate dependencies. The minimal dependency footprint with digest for the main functionality and sha2/testing tools only for development is well-designed.

core/src/payload/bip322.rs (1)

6-17: LGTM! Clean and consistent payload extraction implementation.

The implementation correctly follows the established pattern for payload extraction, parsing the JSON message field with proper error handling. The similarity to ERC-191 (as noted in the comment) ensures consistency across the codebase.

bip340/src/tagged.rs (2)

3-13: LGTM! Correct BIP-340 tagged hash implementation.

The implementation properly follows the BIP-340 specification by computing the tag digest once and then feeding it twice to initialize a new digest instance. The generic approach using the Digest trait provides good flexibility.


22-32: LGTM! Comprehensive test validates the implementation.

The test correctly verifies that the tagged method produces the same result as manually chaining the tag digest twice, ensuring the implementation matches the BIP-340 specification.

bip322/Cargo.toml (2)

20-20: Good architectural decision documented.

The comment about using NEAR SDK host functions exclusively for cryptographic operations is an important architectural constraint that ensures compatibility with the NEAR runtime environment.


17-18: All external dependencies are up-to-date and free of known vulnerabilities

The bs58 = "0.5" requirement covers the latest 0.5.1 release, and bech32 = "0.11" matches the current 0.11.0 version. A query against the Rust security advisories shows no reported issues for either crate.

• No changes needed in bip322/Cargo.toml (lines 17–18).

tests/src/utils/crypto.rs (3)

7-8: LGTM: Clean imports for BIP-322 integration.

The imports are well-organized and bring in the necessary BIP-322 types for the test utility implementation.


17-17: LGTM: Appropriate signature for BIP-322 test method.

The method signature taking a String message and returning SignedBip322Payload is appropriate for BIP-322's message-based signing approach.


71-91: Well-implemented dummy BIP-322 signer for testing.

The implementation correctly creates a dummy BIP-322 signature structure with:

  • Proper P2WPKH address format
  • Hardcoded but valid pubkey hash for consistency
  • Empty witness (appropriate for testing)
  • Clear documentation that this is for testing only

The approach is sound for test utilities where actual cryptographic signing isn't required.

core/src/payload/multi.rs (4)

1-1: LGTM: Proper import for BIP-322 integration.

The import of SignedBip322Payload is correctly placed and necessary for the new enum variant.


75-75: LGTM: Consistent trait implementation for BIP-322.

The Payload trait implementation correctly delegates the hash() method to the inner SignedBip322Payload, maintaining consistency with other variants.


93-93: LGTM: Correct public key type mapping for Bitcoin.

The SignedPayload trait implementation correctly maps BIP-322 verification results to PublicKey::Secp256k1, which is appropriate for Bitcoin's cryptographic scheme.


114-114: LGTM: Complete trait implementation coverage.

The ExtractDefusePayload trait implementation properly handles the BIP-322 variant, ensuring consistent behavior across all payload extraction scenarios.

bip340/src/double.rs (3)

1-4: LGTM: Clean generic double hashing abstraction.

The Double<D> struct provides a clean generic wrapper for double hashing functionality. The implementation approach using composition is sound.


47-49: LGTM: Good use of known test vectors.

The test validates the double SHA-256 implementation against a known expected output for empty input, which is a good cryptographic testing practice.


26-30: Double hashing implementation verified for Bitcoin SHA-256d
The finalize_into method in bip340/src/double.rs (lines 26–30) correctly performs a double SHA-256 and matches the known empty-input test vector (5df6e0e2761359d…4c9456). No further changes required.

bip322/tests/integration_test.rs (5)

1-12: Excellent test documentation and scope definition.

The comprehensive header documentation clearly explains the integration test objectives and scope, making it easy to understand what aspects of BIP-322 integration are being validated.


20-23: Smart compile-time trait verification approach.

The verify_traits_implemented helper function provides a clean way to verify trait implementations at compile time, ensuring that BIP-322 properly implements the required traits.


36-60: Thorough test of Defuse payload extraction integration.

The test properly validates that BIP-322 can carry JSON-encoded Defuse payloads and that the ExtractDefusePayload trait works correctly. The test approach is sound - it doesn't require valid signatures since it's testing payload extraction, not verification.


86-124: Comprehensive validation of core trait implementations.

This test thoroughly validates the Payload and SignedPayload trait implementations with excellent coverage:

  • Hash length validation (32 bytes)
  • Non-zero hash verification
  • Hash determinism testing
  • Message sensitivity verification
  • Proper error handling for empty signatures

The test logic is sound and covers all critical aspects of the trait implementations.


136-226: Excellent MultiPayload integration testing.

This test provides comprehensive validation of BIP-322 integration within the MultiPayload enum:

  • Proper enum variant wrapping
  • Correct delegation of hash and verification methods
  • Hash consistency between direct and wrapped calls
  • Pattern matching validation
  • ExtractDefusePayload trait functionality through the enum wrapper

The test coverage is thorough and validates all critical integration points.

bip322/src/der.rs (1)

79-136: Well-implemented DER ECDSA signature parser.

The function correctly implements full ASN.1 DER parsing with comprehensive validation:

  • Proper tag validation (SEQUENCE 0x30, INTEGER 0x02)
  • Length consistency checks
  • Bounds validation at each step

This implementation follows best practices for secure parsing.

bip322/src/error.rs (1)

1-321: Excellent comprehensive error handling design.

The error module demonstrates best practices:

  • Well-categorized error types with clear separation of concerns
  • Rich contextual information for debugging (indices, hex strings, descriptions)
  • Consistent Display implementations with helpful messages
  • Proper std::error::Error trait implementations

This level of detail will greatly aid in debugging and integration.

bip322/src/bitcoin_minimal.rs (2)

54-88: Correct implementation of Bitcoin cryptographic primitives.

The double_sha256 and hash160 functions correctly implement Bitcoin's standard hash functions using NEAR SDK host functions. This approach is optimal for gas efficiency on NEAR.


315-369: Script construction is correct but has the same zero-array fallback issue.

The script_pubkey() method correctly constructs Bitcoin scripts for each address type, following the proper opcode sequences. However, it suffers from the same issue of using zero-filled arrays as fallbacks for missing data.

Once the to_address_data() issue is fixed, this method should also be updated to handle missing data properly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
bip322/src/der.rs (1)

160-220: The total length validation concern has been properly addressed.

The implementation now includes comprehensive validation (lines 209-217) that ensures:

  • The actual content length matches the declared total length
  • No trailing data exists after the signature
  • The total bytes consumed match the input length

This addresses the previous review comment about preventing malformed signatures with trailing garbage data.

bip322/src/bitcoin_minimal.rs (3)

286-321: The zero-filled array issue has been properly addressed.

The to_address_data() method now correctly returns Result<AddressData, AddressError> and uses ok_or(AddressError::MissingRequiredData) instead of unwrap_or with zero arrays. This ensures missing cryptographic data is handled explicitly and safely.


831-908: Witness data serialization has been properly implemented.

The consensus_encode method now correctly serializes witness data:

  • Detects witness data presence (line 836)
  • Writes witness marker and flag bytes (0x00, 0x01) when needed
  • Serializes witness stacks for all inputs after the outputs

This addresses the previous concern about missing witness data serialization for segwit transactions.


996-1111: BIP-143 segwit v0 sighash algorithm has been correctly implemented.

The segwit_v0_encode_signing_data_to method now properly implements the BIP-143 specification:

  • Writes all 10 required fields in the correct order
  • Uses helper methods to compute hashPrevouts, hashSequence, and hashOutputs
  • Correctly serializes sighash type as a 4-byte little-endian integer
  • Includes proper bounds checking for input_index

This addresses the previous concern about incorrect BIP-143 implementation.

🧹 Nitpick comments (2)
bip322/src/der.rs (1)

210-210: Fix formatting issue.

Run cargo fmt to fix the formatting issue on this line.

bip322/src/bitcoin_minimal.rs (1)

833-833: Fix formatting issues throughout the file.

Run cargo fmt to fix the formatting issues at multiple lines flagged by static analysis.

Also applies to: 888-888, 1035-1035, 1066-1066, 1079-1079, 1091-1091

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61f54f3 and 7fcde90.

📒 Files selected for processing (4)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/der.rs (1 hunks)
  • core/src/payload/multi.rs (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • bip322/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/payload/multi.rs
🧰 Additional context used
🪛 GitHub Check: Format
bip322/src/der.rs

[warning] 210-210:
Diff in /home/runner/work/intents/intents/bip322/src/der.rs

bip322/src/bitcoin_minimal.rs

[warning] 1091-1091:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 1079-1079:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 1066-1066:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 1035-1035:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 888-888:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 833-833:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Security Audit - report
🔇 Additional comments (5)
bip322/src/der.rs (3)

31-65: LGTM! Well-implemented DER length parsing with proper validation.

The function correctly handles both short and long form DER length encoding with comprehensive validation for canonical and minimal encoding rules.


88-145: Robust DER ECDSA signature parsing with comprehensive validation.

The function properly validates the complete DER structure and ensures no trailing data is accepted by checking that pos + total_len == der_bytes.len() at line 109.


222-355: Excellent test coverage for DER parsing functions.

The tests comprehensively cover:

  • Valid and invalid DER length encodings
  • Canonical encoding validation
  • Trailing data detection for both parsers
  • Various malformed signature structures

Particularly good to see specific tests for the trailing data issue mentioned in previous reviews.

bip322/src/bitcoin_minimal.rs (2)

54-88: Well-implemented cryptographic functions using NEAR SDK.

Both double_sha256 and hash160 correctly implement Bitcoin's standard hash functions using NEAR SDK's optimized host functions for gas efficiency.


391-562: Comprehensive and correct Bitcoin address parsing.

The implementation properly handles all supported address types with full validation:

  • Base58Check decoding with checksum verification for P2PKH/P2SH
  • Bech32 decoding for segwit addresses
  • Proper version byte and length validation
  • Clear error handling for each failure mode

Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
bip322/src/der.rs (1)

289-300: Consider adding tests with realistic ECDSA signature sizes.

The current tests use minimal values (R=0x01, S=0x02) which are good for structural validation. Consider adding tests with realistic Bitcoin signature sizes where R and S are typically 32-33 bytes each, to ensure the parsing handles real-world signatures correctly.

Example test case:

#[test]
fn test_parse_der_ecdsa_signature_realistic_size() {
    // Realistic 71-byte DER signature (common in Bitcoin)
    let realistic_der = vec![
        0x30, 0x45, // SEQUENCE, length 69
        0x02, 0x21, // INTEGER, length 33 (R with leading zero for sign)
        0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11,
        0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22,
        0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
        0x02, 0x20, // INTEGER, length 32 (S)
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
        0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11,
        0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22,
    ];
    
    let result = parse_der_ecdsa_signature(&realistic_der);
    assert!(result.is_some());
    let (r, s) = result.unwrap();
    assert_eq!(r.len(), 33);
    assert_eq!(s.len(), 32);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fcde90 and 44d8cf3.

📒 Files selected for processing (2)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/der.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • bip322/src/bitcoin_minimal.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Build
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Security Audit - report

Copy link

@coderabbitai coderabbitai bot left a 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 (2)
bip322/src/lib.rs (1)

607-2563: LGTM! Comprehensive test coverage with excellent edge case handling.

The test suite thoroughly covers address parsing, transaction structure, error conditions, Unicode handling, and network validation. The inclusion of official BIP-322 test vectors is particularly valuable.

Consider adding integration tests with real Bitcoin signatures to validate the complete signing and verification flow end-to-end. This could be done by:

  1. Using known test vectors with real signatures from BIP-322 reference implementations
  2. Or integrating with a Bitcoin signing library in tests to generate valid signatures

Example test structure:

#[test]
fn test_real_signature_verification() {
    // Test vector from BIP-322 reference implementation
    let test_vector = SignedBip322Payload {
        address: "bc1q...".parse().unwrap(),
        message: "Hello World".to_string(),
        signature: Witness::from_stack(vec![
            hex!("30440220..."), // Real DER signature
            hex!("02..."),      // Real public key
        ]),
    };
    
    let recovered_pubkey = test_vector.verify();
    assert!(recovered_pubkey.is_some(), "Real signature should verify");
    // Verify the recovered pubkey matches expected
}
bip322/src/bitcoin_minimal.rs (1)

351-406: Consider returning Result from script_pubkey() to avoid panics.

The method uses expect() calls that could panic if required data is missing. While the documentation states to call to_address_data() first, this creates a potential footgun.

Consider making this method return Result<ScriptBuf, AddressError>:

-pub fn script_pubkey(&self) -> ScriptBuf {
+pub fn script_pubkey(&self) -> Result<ScriptBuf, AddressError> {
     match self.address_type {
         AddressType::P2PKH => {
-            let pubkey_hash = self.pubkey_hash.expect("P2PKH address missing pubkey_hash");
+            let pubkey_hash = self.pubkey_hash.ok_or(AddressError::MissingRequiredData)?;
             // ... rest of implementation
-            ScriptBuf { inner: script }
+            Ok(ScriptBuf { inner: script })
         }
         // ... other cases
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44d8cf3 and 3f11c76.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • bip322/Cargo.toml (1 hunks)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/error.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • bip322/Cargo.toml
  • bip322/README.md
  • bip322/src/error.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Build
  • GitHub Check: Security Audit - deny
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Security Audit - report
🔇 Additional comments (19)
bip322/src/lib.rs (7)

1-16: LGTM! Well-organized imports with proper feature gating.

The module structure and imports are clean and appropriate for BIP-322 implementation on NEAR.


18-41: LGTM! Well-structured payload definition with comprehensive documentation.

The witness format documentation clearly explains the expected structure for each address type, which is crucial for proper usage.


43-53: LGTM! Clean implementation of the Payload trait.

The pattern matching correctly delegates to address-specific hash methods.


55-66: LGTM! Consistent implementation of the SignedPayload trait.

The verification routing mirrors the hash computation pattern appropriately.


174-307: LGTM! Transaction creation follows BIP-322 specification accurately.

The implementation correctly creates the virtual transactions with proper version markers, input/output structures, and cross-references.


309-341: LGTM! Correct implementation of BIP-322 tagged message hash.

The tagged hash implementation properly follows the BIP-340 pattern with domain separation.


512-605: Move helper methods out of test module.

These methods are needed for the main implementation (script validation) and should not be in the test module.

Move these helper methods to the main impl SignedBip322Payload block:

-#[cfg(test)]
-mod tests {
-    // ... test imports ...
-
-    impl SignedBip322Payload {
-        fn execute_redeem_script(redeem_script: &[u8], pubkey_bytes: &[u8]) -> bool {
-            // ... implementation ...
-        }
-        // ... other helper methods ...
-    }
+impl SignedBip322Payload {
+    fn execute_redeem_script(redeem_script: &[u8], pubkey_bytes: &[u8]) -> bool {
+        // ... implementation ...
+    }
+    
+    fn execute_witness_script(witness_script: &[u8], pubkey_bytes: &[u8]) -> bool {
+        // ... implementation ...
+    }
+    
+    fn verify_pubkey_matches_address(&self, pubkey_bytes: &[u8]) -> bool {
+        // ... implementation ...
+    }
+    
+    fn is_valid_public_key_format(pubkey_bytes: &[u8]) -> bool {
+        // ... implementation ...
+    }
+    
+    fn compute_pubkey_hash160(pubkey_bytes: &[u8]) -> [u8; 20] {
+        hash160(pubkey_bytes)
+    }
+}

+#[cfg(test)]
+mod tests {

Likely an incorrect or invalid review comment.

bip322/src/bitcoin_minimal.rs (12)

1-38: Excellent module documentation and appropriate imports.

The comprehensive documentation clearly explains the design principles, supported address types, and integration with NEAR SDK. The imports are well-chosen for the specific functionality needed.


39-83: Well-implemented NEAR SDK integration with digest traits.

The NearSha256 implementation correctly integrates NEAR's host functions with the digest crate's trait system, enabling compatibility with BIP340 functionality while maintaining gas efficiency.


84-111: Correct HASH160 implementation using NEAR SDK.

The function correctly implements Bitcoin's HASH160 algorithm (RIPEMD160(SHA256(data))) using NEAR's optimized host functions, with excellent documentation.


112-260: Well-designed address structures with comprehensive documentation.

The Address struct and related types are thoughtfully designed with appropriate use of Option types for different address formats. The documentation clearly explains each field's purpose and usage.


318-343: Excellent error handling for missing cryptographic data.

The to_address_data() method correctly addresses the previous security concern by returning proper errors instead of using zero-filled arrays as fallbacks. This ensures missing cryptographic data is handled explicitly and safely.


442-583: Comprehensive and secure address parsing implementation.

The parsing logic correctly validates all supported address formats with proper checksum verification, length validation, and network validation. The use of NearDoubleSha256 for Base58Check validation and the bech32 crate for segwit addresses ensures correctness.


590-716: Excellent error handling and Bech32 validation.

The comprehensive AddressError enum provides specific error types for different failure modes. The decode_bech32 function correctly validates witness program constraints per BIP-141 and ensures mainnet-only operation.


717-846: Standard Bitcoin transaction structures.

The transaction structures correctly follow Bitcoin's format with appropriate field types and minimal implementation suitable for BIP-322 verification.


853-931: Complete transaction encoding with witness support.

The consensus encoding implementation correctly serializes both legacy and witness transaction formats, properly including witness marker/flag bytes and witness data serialization as required by BIP-322.


1038-1089: Correct BIP-143 segwit v0 sighash implementation.

The sighash preimage encoding now correctly follows the BIP-143 specification with proper field ordering, double SHA-256 computations for hash fields, and compact size encoding. This addresses the previous review concern about incorrect implementation.


1095-1133: Well-implemented BIP-143 hash computation methods.

The helper methods correctly compute hashPrevouts, hashSequence, and hashOutputs using double SHA-256 as specified in BIP-143, with proper data serialization and error handling.


1149-1212: Comprehensive test coverage for core functionality.

The tests properly verify NEAR SDK integration, BIP340 compatibility, and address parsing with appropriate test vectors and parameterized testing using rstest.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
bip322/src/lib.rs (4)

68-172: Critical: Incorrect sighash algorithm for P2PKH and P2SH addresses.

This is a duplicate of a previous critical issue. The implementation uses segwit v0 sighash algorithm for all address types, but P2PKH and P2SH should use the legacy sighash algorithm. The compute_message_hash method (lines 353-377) only implements segwit v0 sighash computation.


422-441: Critical: Missing redeem script validation for P2SH addresses.

This is a duplicate of a previous critical issue. The P2SH verification extracts the redeem script but never validates it. According to BIP-322, you must verify that HASH160(redeem_script) == script_hash from the address and execute the redeem script to validate the signature.


443-463: Critical: Missing witness script validation for P2WSH addresses.

This is a duplicate of a previous critical issue. The witness script is extracted but never validated or executed. You need to validate that the witness script hash matches the address and execute the script according to BIP-322.


465-491: Critical: Limited signature format support.

This is a duplicate of a previous critical issue. The implementation only accepts raw 64-byte signatures and recovery IDs 0-1, but Bitcoin signatures are typically in DER format and recovery IDs should range from 0-3.

🧹 Nitpick comments (4)
bip322/src/lib.rs (2)

943-949: Fix unnecessary closure usage.

The static analysis tool correctly identified an unnecessary closure. The unwrap_or_else can be replaced with unwrap_or since you're providing a constant value, not calling a function.

-            address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l"
-                .parse()
-                .unwrap_or_else(|_| Address {
-                    address_type: AddressType::P2WPKH,
-                    pubkey_hash: Some([1u8; 20]),
-                    witness_program: None,
-                }),
+            address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l"
+                .parse()
+                .unwrap_or(Address {
+                    address_type: AddressType::P2WPKH,
+                    pubkey_hash: Some([1u8; 20]),
+                    witness_program: None,
+                }),

512-605: Note: Limited script execution capabilities.

The execute_redeem_script and execute_witness_script methods only support simple P2PKH-style scripts. This is documented as a limitation with plans for "full Bitcoin script interpreter" in the future. For production use, consider implementing more comprehensive script validation.

bip322/src/bitcoin_minimal.rs (2)

344-399: Consider making script_pubkey() return Result to avoid panics.

While the documentation states to call to_address_data() first, the expect() calls in script_pubkey() could still panic if callers don't follow this convention.

Consider returning Result<ScriptBuf, AddressError>:

-pub fn script_pubkey(&self) -> ScriptBuf {
+pub fn script_pubkey(&self) -> Result<ScriptBuf, AddressError> {
     match self.address_type {
         AddressType::P2PKH => {
-            let pubkey_hash = self.pubkey_hash.expect("P2PKH address missing pubkey_hash");
+            let pubkey_hash = self.pubkey_hash.ok_or(AddressError::MissingRequiredData)?;
             // ... rest of implementation
-            ScriptBuf { inner: script }
+            Ok(ScriptBuf { inner: script })
         }
         // ... similar changes for other variants
     }
 }

This would provide consistent error handling throughout the API.


1138-1200: Good test coverage for core functionality.

The existing tests properly verify NEAR SDK integration, BIP340 compatibility, and basic address parsing. The use of rstest for parameterized testing is excellent.

Consider adding tests for:

  • Error conditions in address parsing (invalid checksums, wrong lengths)
  • Edge cases in transaction encoding
  • Sighash computation with different transaction structures
  • Script generation for all address types

This would provide more comprehensive coverage of the module's functionality.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f11c76 and 5008da1.

📒 Files selected for processing (3)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/tests/integration_test.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • bip322/tests/integration_test.rs
🧰 Additional context used
🪛 GitHub Check: Check
bip322/src/lib.rs

[failure] 943-943:
unnecessary closure used to substitute value for Result::Err

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (9)
bip322/src/lib.rs (3)

18-66: LGTM: Well-structured BIP-322 payload implementation.

The struct definition is properly documented and annotated. The trait implementations correctly dispatch verification and hashing based on address type, which is essential for BIP-322 compliance.


174-351: LGTM: Correct BIP-322 transaction construction.

The transaction creation methods properly implement the BIP-322 specification:

  • Uses version 0 as BIP-322 marker
  • Creates proper "to_spend" transaction with tagged message hash
  • Creates "to_sign" transaction that references "to_spend"
  • Implements BIP-322 tagged hash correctly following BIP-340 pattern
  • Uses OP_RETURN for provably unspendable output

607-2543: Excellent: Comprehensive test coverage.

The test suite demonstrates exceptional thoroughness with:

  • Gas benchmarking for NEAR blockchain optimization
  • BIP-322 official test vectors for specification compliance
  • Comprehensive error handling and edge cases
  • Unicode message support testing
  • Network interoperability restrictions
  • Cross-address type verification security

This level of testing significantly improves confidence in the implementation quality.

bip322/src/bitcoin_minimal.rs (6)

39-82: Well-designed NEAR SDK digest integration.

The implementation correctly bridges NEAR SDK's env::sha256_array() with the standard digest crate traits, enabling compatibility with BIP340's Double wrapper for Bitcoin's double SHA-256. The buffer-based approach is standard for digest implementations.


104-110: Correct HASH160 implementation using NEAR SDK.

The implementation correctly follows Bitcoin's HASH160 algorithm (RIPEMD160(SHA256(data))) using NEAR SDK host functions for gas efficiency. This is essential for address generation and validation.


311-336: Excellent error handling for missing cryptographic data.

The implementation correctly addresses the previous security concern by returning Result<AddressData, AddressError> instead of using zero-filled arrays as fallbacks. This ensures missing cryptographic data is handled explicitly and safely.


435-573: Comprehensive and secure address parsing implementation.

The parsing logic correctly handles all supported address types with proper validation:

  • Base58Check validation with checksum verification for P2PKH/P2SH
  • Bech32 validation with witness program length checks for P2WPKH/P2WSH
  • Network validation (mainnet only)
  • Detailed error reporting

The implementation follows Bitcoin standards precisely and provides robust error handling.


842-920: Proper transaction serialization with witness support.

The implementation correctly addresses the previous concern about missing witness data serialization. The encoding now properly:

  • Detects witness data presence
  • Includes witness marker/flag bytes when needed
  • Serializes witness stacks for each input
  • Follows Bitcoin consensus format

This is essential for BIP-322 with segwit addresses.


1027-1123: Correct BIP-143 sighash implementation.

The implementation now properly follows the BIP-143 specification for segwit v0 sighash computation. It correctly writes the preimage format with all required fields in the proper order and uses the correct helper functions for computing hashPrevouts, hashSequence, and hashOutputs. This addresses the previous review concern about incorrect sighash algorithm implementation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
bip322/src/lib.rs (4)

71-175: Critical: Incorrect sighash algorithm still not fixed.

All hash computation methods delegate to the same compute_message_hash function (lines 360-384), which only implements segwit v0 sighash algorithm. This is incorrect for P2PKH and P2SH addresses, which should use the legacy sighash algorithm according to BIP-322.

The issue identified in previous reviews has not been addressed.


360-384: Critical: Sighash algorithm issue persists.

The compute_message_hash method still only implements segwit v0 sighash algorithm for all address types. This is the same critical issue identified in previous reviews that remains unaddressed.

P2PKH and P2SH addresses require legacy sighash algorithm, not segwit v0.


463-483: Critical: P2WSH witness script validation still missing.

The witness script is extracted (line 472) but never validated or executed. This is the same critical security issue identified in previous reviews that remains unaddressed.

According to BIP-322, you must:

  1. Verify that SHA256(witness_script) matches the witness program
  2. Execute the witness script to validate the signature

485-511: Critical: Limited signature format support persists.

The signature recovery method still has the same limitations identified in previous reviews:

  1. Only accepts raw 64-byte signatures (line 492), not DER format
  2. Only tries recovery IDs 0-1 (line 497), should be 0-3 for Bitcoin standard

This limits compatibility with real Bitcoin signatures.

🧹 Nitpick comments (1)
bip322/src/bitcoin_minimal.rs (1)

1138-1200: Good foundation but consider expanding test coverage.

The existing tests properly validate core functionality including NEAR SDK SHA256 compatibility, BIP340 tagged hashing, and basic address parsing. However, consider adding tests for:

  • Error conditions in address parsing (invalid checksums, wrong lengths, unsupported formats)
  • Transaction encoding/decoding round-trips
  • Sighash computation with known test vectors
  • Edge cases in witness program validation
  • Script pubkey generation for all address types

The current tests provide a solid foundation, but more comprehensive coverage would increase confidence in the implementation's correctness across all supported scenarios.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84e88bc and 6bcbfca.

📒 Files selected for processing (2)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
bip322/src/lib.rs (3)
near-utils/src/digest.rs (1)
  • digest (44-47)
bip322/src/bitcoin_minimal.rs (16)
  • hash160 (104-110)
  • new (51-53)
  • new (274-276)
  • new (716-718)
  • new (751-753)
  • new (963-965)
  • new (1004-1006)
  • len (278-280)
  • len (724-726)
  • from_str (431-568)
  • script_pubkey (344-395)
  • is_empty (282-284)
  • is_empty (720-722)
  • is_p2wsh (249-251)
  • is_p2wpkh (245-247)
  • from_stack (291-293)
core/src/payload/multi.rs (2)
  • hash (67-78)
  • verify (85-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Build
  • GitHub Check: Security Audit - deny
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (10)
bip322/src/lib.rs (6)

1-19: LGTM!

The module structure and imports are well-organized and comprehensive. The conditional compilation attributes for ABI features and the re-exports from submodules are properly handled.


20-43: LGTM!

The SignedBip322Payload struct is well-defined with clear documentation of the witness format requirements for different address types. The serde attributes and conditional compilation are properly configured.


45-69: LGTM!

The trait implementations use a clean delegation pattern based on address type, which properly separates concerns and makes the code maintainable. The error handling with expect is appropriate since address validation should have occurred during construction.


177-357: LGTM on transaction creation structure!

The BIP-322 transaction creation follows the specification correctly:

  • to_spend transaction properly embeds the tagged message hash
  • to_sign transaction correctly references the to_spend output
  • Tagged hash implementation uses proper domain separation
  • Transaction structure matches BIP-322 requirements

429-461: Good improvement on P2SH validation!

The P2SH signature verification now properly:

  • Validates that the redeem script hash matches the address (lines 441-444)
  • Executes the redeem script to verify it's a supported pattern (lines 447-450)

This addresses the critical issue from previous reviews regarding missing redeem script validation.


555-2601: Excellent comprehensive test suite!

The test coverage is outstanding with:

  • Gas benchmarking for performance validation
  • Address parsing and validation tests for all Bitcoin address types
  • Error handling tests for malformed inputs
  • Unicode message handling tests
  • Network interoperability tests ensuring mainnet-only operation
  • Transaction serialization tests for both witness and legacy formats
  • Cross-address-type verification tests

The tests demonstrate thorough consideration of edge cases and security boundaries.

bip322/src/bitcoin_minimal.rs (4)

39-110: LGTM! Well-designed NEAR SDK cryptographic integration.

The NearSha256 implementation correctly integrates with NEAR's host functions while maintaining compatibility with the digest crate traits and BIP340. The hash160 function properly implements Bitcoin's standard RIPEMD160(SHA256(data)) using NEAR SDK functions. This design optimizes for NEAR's gas model while ensuring cryptographic correctness.


402-701: Excellent comprehensive address parsing implementation.

The FromStr implementation for Address correctly handles all major Bitcoin address types (P2PKH, P2SH, P2WPKH, P2WSH) with proper validation:

  • Base58Check decoding with version byte and checksum validation for legacy addresses
  • Bech32 decoding with HRP validation and witness program length checks for segwit addresses
  • Comprehensive error handling that provides specific failure reasons
  • Mainnet-only validation as intended for the MVP scope

The previous security concern about zero-filled fallbacks has been properly addressed with explicit error handling.


838-949: LGTM! Complete Bitcoin transaction serialization implementation.

The Encodable implementation for Transaction correctly follows Bitcoin's consensus format including:

  • Proper witness transaction serialization with marker/flag bytes (0x00, 0x01)
  • Correct field ordering and encoding (version, inputs, outputs, witness data, locktime)
  • Compact size encoding for variable-length fields
  • Support for both legacy and witness transaction formats

The previous issue with missing witness data serialization has been properly addressed. This implementation ensures compatibility with Bitcoin's transaction format standards.


1027-1123: Excellent BIP-143 sighash implementation.

The segwit_v0_encode_signing_data_to method correctly implements the BIP-143 sighash preimage format with all required fields in proper order:

  1. Transaction version, hashPrevouts, hashSequence
  2. Specific input's outpoint, scriptCode with compact size prefix
  3. Amount, input sequence, hashOutputs
  4. Locktime and sighash_type as 4-byte little-endian

The helper functions properly compute the required hash values using double SHA256. Input bounds checking prevents out-of-bounds access. The previous issue with incorrect BIP-143 implementation has been resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (24)
bip322/src/error.rs (2)

40-41: Document the UnsupportedWitnessVersion variant for consistency and clarity

All other variants have helpful doc comments. Add a brief description and reference to BIPs 173/350 for v0 vs. v1+ rules.

-    UnsupportedWitnessVersion,
+    /// Unsupported witness version (e.g., v1+ which requires Bech32m per BIP-350).
+    /// See BIP-173/BIP-350 for valid combinations and encoding rules.
+    UnsupportedWitnessVersion,

52-61: Minor copy edit: capitalize “Bech32” in error message

Matches common usage and your other capitalization patterns.

-            Self::InvalidBech32 => write!(f, "Invalid bech32 encoding"),
+            Self::InvalidBech32 => write!(f, "Invalid Bech32 encoding"),
bip322/Cargo.toml (1)

18-22: Confirm necessity of runtime base64

base64 = "0.22" is included in both dependencies and dev-dependencies. If the crate itself doesn’t use base64 at runtime (only tests/tools do), consider moving it exclusively to [dev-dependencies] to keep the runtime surface smaller.

If helpful, I can scan the crate for base64 usage and propose a tidy-up.

bip322/validate_unisat_vector.py (6)

15-29: Strengthen Bech32 parsing and avoid bare except

  • Validate witness version (expect 0 for P2WPKH/P2WSH).
  • Support both 20 (P2WPKH) and 32 (P2WSH) lengths if desired.
  • Replace bare except: with except Exception as e: and log for visibility.
 def parse_bech32_address(address):
     """Parse bech32 address to get witness program"""
     # Simple bech32 parsing (for P2WPKH)
     import bech32
     
     try:
         hrp, data = bech32.bech32_decode(address)
-        if hrp == 'bc' and data:
-            # Convert from 5-bit to 8-bit
-            decoded = bech32.convertbits(data[1:], 5, 8, False)
-            if decoded and len(decoded) == 20:
-                return bytes(decoded)
-    except:
-        pass
+        if hrp != 'bc' or not data:
+            return None
+        version = data[0]
+        if version != 0:
+            # Only v0 (Bech32) supported in this simplified parser
+            return None
+        # Convert from 5-bit to 8-bit
+        decoded = bech32.convertbits(data[1:], 5, 8, False)
+        if not decoded:
+            return None
+        # Accept P2WPKH (20) or P2WSH (32) witness program sizes
+        if len(decoded) in (20, 32):
+            return bytes(decoded)
+    except Exception as e:
+        print(f"bech32 parse error: {e}")
     return None

6-10: Remove unused import

unhexlify is unused.

-import base64
-import hashlib
-from binascii import hexlify, unhexlify
+import base64
+import hashlib
+from binascii import hexlify

101-124: Stub returns True; consider at least checking HASH160 when available

This function currently always returns True, which can mask invalid cases. If hashlib.new('ripemd160') is available, you can compute HASH160 to compare with the witness program. Otherwise, make the placeholder explicit with a FIXME to avoid accidental reliance.

I can provide a small fallback-based implementation that uses hashlib.new('ripemd160') when present and degrades gracefully otherwise.


147-159: Avoid f-strings without placeholders

This f-string has no placeholders, which linters flag.

-        print(f"✓ Address parsed successfully")
+        print("✓ Address parsed successfully")

165-179: Remove or use the unused variable and avoid unused imports for availability checks

  • recovered_pubkey is assigned but not used.
  • For availability checks, prefer importlib.util.find_spec over importing modules you don’t use (avoids F401).
-    recovered_pubkey = recover_pubkey_from_signature(message_hash, signature_bytes)
+    _ = recover_pubkey_from_signature(message_hash, signature_bytes)
@@
-    try:
-        import bech32
-        print("✓ bech32 library available")
-    except ImportError:
-        print("✗ bech32 library not available - run: pip install bech32")
+    import importlib.util
+    if importlib.util.find_spec("bech32"):
+        print("✓ bech32 library available")
+    else:
+        print("✗ bech32 library not available - run: pip install bech32")
@@
-    try:
-        import ecdsa
-        print("✓ ecdsa library available")
-    except ImportError:
-        print("✗ ecdsa library not available - run: pip install ecdsa")
+    if importlib.util.find_spec("ecdsa"):
+        print("✓ ecdsa library available")
+    else:
+        print("✗ ecdsa library not available - run: pip install ecdsa")

54-100: Placeholder recovery routine

The recovery function prints diagnostics and returns None. That’s fine for an exploratory script, but call it out as a deliberate stub to avoid confusion. If you want parity with the comprehensive script, consider factoring common bits or guarding with a “--verbose” flag.

Happy to help replace this with a concrete secp256k1 recovery using coincurve or bitcoinlib if acceptable for your environment.

bip322/validate_unisat_comprehensive.py (4)

15-29: Harden Bech32 parsing and avoid bare except

As in the vector script, validate witness version and length, and avoid bare except.

 def parse_bech32_address(address):
     """Parse bech32 address to get witness program"""
     import bech32
     
     try:
         hrp, data = bech32.bech32_decode(address)
-        if hrp == 'bc' and data:
-            # Convert from 5-bit to 8-bit
-            decoded = bech32.convertbits(data[1:], 5, 8, False)
-            if decoded and len(decoded) == 20:
-                return bytes(decoded)
-    except:
-        pass
+        if hrp != 'bc' or not data:
+            return None
+        version = data[0]
+        if version != 0:
+            return None
+        # Convert from 5-bit to 8-bit
+        decoded = bech32.convertbits(data[1:], 5, 8, False)
+        if not decoded:
+            return None
+        if len(decoded) in (20, 32):
+            return bytes(decoded)
+    except Exception as e:
+        print(f"bech32 parse error: {e}")
     return None

6-10: Remove unused import

unhexlify is unused.

-import base64
-import hashlib
-from binascii import hexlify, unhexlify
+import base64
+import hashlib
+from binascii import hexlify

53-180: Trim unused imports and variables in recovery path

  • possible_public_keys_from_signature and Key are imported but unused.
  • point is assigned but not used.
  • Consider consolidating recovery logic or gating heavy debug under a verbosity switch.
-        import ecdsa
-        from ecdsa.curves import SECP256k1
-        from ecdsa.ecdsa import possible_public_keys_from_signature
+        import ecdsa
+        from ecdsa.curves import SECP256k1
@@
-            from bitcoinlib.encoding import hash160
-            from bitcoinlib.keys import Key
+            from bitcoinlib.encoding import hash160
@@
-                            point = Point(SECP256k1.curve, x, y, order)
+                            _ = Point(SECP256k1.curve, x, y, order)

201-206: Avoid f-strings without placeholders

Minor linter cleanup.

-        print(f"✓ Address parsed successfully")
-        print(f"Expected witness program: {hexlify(witness_program).decode()}")
+        print("✓ Address parsed successfully")
+        print(f"Expected witness program: {hexlify(witness_program).decode()}")
bip322/src/transaction.rs (2)

49-54: Nit: clarify the comment about script length; it’s 1 opcode + 1 push length + 32 bytes

The comment says “2 opcodes + 32 bytes message hash” but the second byte is the push length (0x20), not an opcode.

Apply this diff to make the comment precise:

-                let mut script = Vec::with_capacity(34); // 2 opcodes + 32 bytes message hash
-                script.push(OP_0); // Push empty stack item
-                script.push(32u8); // Push 32 bytes
+                let mut script = Vec::with_capacity(34); // OP_0 + push(32) + 32-byte message hash
+                script.push(OP_0); // push empty stack item
+                script.push(32u8); // next 32 bytes are pushed as data

154-161: Txid vs wtxid nuance; consider documenting or enforcing no-witness serialization

The txid in Bitcoin is the double-SHA256 of the non-witness serialization. With your current approach, if a Transaction ever carries non-empty witnesses, consensus_encode may produce the segwit-serialization (marker/flag + witness), yielding a wtxid instead of a txid. In this module you only build empty-witness transactions, so it’s fine, but the helper is generic and could be reused.

At minimum, document that tx must have empty witnesses or implement a non-witness encoder to make it bulletproof.

Apply this diff to document the constraint:

-/// Computes the transaction ID (TXID) by double SHA256 hashing the serialized transaction.
+/// Computes the transaction ID (TXID) by double SHA256 hashing the serialized transaction.
+/// NOTE: Assumes no witness data is present in `tx`. If any input carries a non-empty witness,
+/// this would hash the segwit serialization (wtxid) rather than the legacy txid.
bip322/src/verification.rs (1)

98-107: Rename local buffer to avoid shadowing the compressed parameter

The local compressed Vec shadows the compressed: bool parameter. Rename to improve readability and avoid confusion.

Apply this diff:

-        let mut compressed = Vec::with_capacity(33);
-        compressed.push(0x02);
-        compressed.extend_from_slice(&raw_pubkey[..32]);
+        let mut comp_buf = Vec::with_capacity(33);
+        comp_buf.push(0x02);
+        comp_buf.extend_from_slice(&raw_pubkey[..32]);
 
         let mut response = Vec::with_capacity(2);
-        response.push(defuse_near_utils::digest::Hash160::digest(&compressed).into());
+        response.push(defuse_near_utils::digest::Hash160::digest(&comp_buf).into());
 
-        compressed.as_mut_slice()[0] = 0x03;
-        response.push(defuse_near_utils::digest::Hash160::digest(&compressed).into());
+        comp_buf.as_mut_slice()[0] = 0x03;
+        response.push(defuse_near_utils::digest::Hash160::digest(&comp_buf).into());
bip322/src/lib.rs (1)

65-76: API naming mismatch: with_compact_signature does not enforce compact-only

The constructor parses either compact or full signatures via from_str. Either tighten the API to ensure compact-only, or rename the constructor to avoid confusion.

Two options:

  • Enforce compact-only:
-        let signature = Bip322Signature::from_str(signature_base64)?;
+        let signature = Bip322Signature::from_str(signature_base64)?;
+        if !matches!(signature, Bip322Signature::Compact { .. }) {
+            return Err(Bip322Error::InvalidFormat);
+        }
  • Or rename the constructor (requires call-site updates):
-    pub fn with_compact_signature(
+    pub fn with_parsed_signature(
bip322/src/hashing.rs (1)

145-154: P2WSH not supported here; accept witness script input instead of panicking

Panicking on P2WSH will be surprising if higher layers ever request a P2WSH sighash. Consider accepting the witness script as an argument (or wiring through from signature parsing) and computing the BIP-143 preimage accordingly. At minimum, document the unsupported state.

Minimal doc update:

-            Address::P2WSH { .. } => {
+            Address::P2WSH { .. } => {
                 // For P2WSH, the scriptCode must be the witness script itself.
                 // It is not derivable from the address; you'll need the script provided.
-                // If you don't support general P2WSH here, you can return a hash that will
-                // never verify, or panic with a clear message.
+                // This helper currently does not support providing the witness script.
+                // Upstream should compute segwit v0 sighash with the witness script
+                // available from the signature and not call this path.
                 panic!("compute_segwit_v0_sighash: P2WSH requires the witness script (not derivable from address)")
             }

Preferred signature change (follow-up PR):

  • Add a variant compute_segwit_v0_sighash_with_script(to_spend, to_sign, witness_script: &[u8]).
  • Dispatch to it when Address::P2WSH is used.
bip322/src/tests.rs (1)

335-353: Test description mismatch: 65-byte is a valid compact signature length

The test name/message imply an invalid signature length, but a 65-byte (r||s||recid) is the expected compact size. The failure here is due to signature content, not its length. Adjust wording to avoid confusion.

Apply this diff:

-    fn test_signature_verification_invalid_signature_length() {
+    fn test_signature_verification_invalid_signature_content() {
@@
-        let invalid_signature = [0u8; 65]; // Valid 65-byte signature (but empty, so will fail)
+        let invalid_signature = [0u8; 65]; // Valid length, invalid content (all zeros)
@@
-            "Invalid signature length should fail verification"
+            "Invalid signature content should fail verification"
bip322/src/signature.rs (2)

85-87: Be more tolerant to base64 input variants (no padding).

Wallets sometimes produce base64 without padding. Consider a fallback to STANDARD_NO_PAD before failing.

-        let decoded = general_purpose::STANDARD.decode(s)?;
+        let decoded = match general_purpose::STANDARD.decode(s) {
+            Ok(b) => b,
+            Err(e) => {
+                // tolerate missing padding
+                general_purpose::STANDARD_NO_PAD
+                    .decode(s)
+                    .map_err(|_| Bip322Error::InvalidBase64(e))?
+            }
+        };

88-96: Avoid panicking in FromStr; return a typed error for invalid 65-byte input.

Even if length-checked, expect is unnecessary in parsing untrusted data. Return InvalidCompactSignature instead of panicking.

-        if decoded.len() == 65 {
-            let sig_bytes: [u8; 65] = decoded.try_into().expect("Invalid signature length"); // Should never fail
-            return Ok(Bip322Signature::Compact {
-                signature: sig_bytes,
-            });
-        }
+        if decoded.len() == 65 {
+            let mut sig_bytes = [0u8; 65];
+            sig_bytes.copy_from_slice(&decoded);
+            return Ok(Bip322Signature::Compact { signature: sig_bytes });
+        }
bip322/src/bitcoin_minimal.rs (3)

15-17: Doc mismatch: code supports P2SH/P2WSH but docs say “supports only P2PKH/P2WPKH”.

Keep the module-level docs in sync with current capabilities to avoid confusion.

Suggested edit:

  • Replace “MVP Focus: Supports only P2PKH and P2WPKH” with a note that P2SH and P2WSH are also supported now.

Also applies to: 23-24


425-431: Same here: avoid const fn for ScriptBuf::new().

For consistency and portability, make this a regular constructor.

-impl ScriptBuf {
-    pub const fn new() -> Self {
-        Self { inner: Vec::new() }
-    }
+impl ScriptBuf {
+    pub fn new() -> Self {
+        Self { inner: Vec::new() }
+    }

393-395: Avoid unwrap() in address parsing; map errors explicitly.

Parsing “bc” is infallible, but unwrap() is unnecessary in critical parsing code.

-    if hrp != Hrp::parse("bc").unwrap() {
+    if Hrp::parse("bc").map_err(|_| AddressError::InvalidBech32)? != hrp {
         return Err(AddressError::InvalidBech32);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a93465a and 05f1d14.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .gitignore (1 hunks)
  • Cargo.toml (2 hunks)
  • bip322/Cargo.toml (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/error.rs (1 hunks)
  • bip322/src/hashing.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/src/signature.rs (1 hunks)
  • bip322/src/tests.rs (1 hunks)
  • bip322/src/transaction.rs (1 hunks)
  • bip322/src/verification.rs (1 hunks)
  • bip322/validate_unisat_comprehensive.py (1 hunks)
  • bip322/validate_unisat_vector.py (1 hunks)
  • near-utils/src/digest.rs (3 hunks)
  • tests/Cargo.toml (1 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • Cargo.toml
  • tests/src/utils/crypto.rs
  • tests/Cargo.toml
🧰 Additional context used
🧬 Code Graph Analysis (9)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (3)
  • compute_tx_id (155-161)
  • create_to_sign (101-141)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (6)
  • new (140-142)
  • new (425-427)
  • new (456-458)
  • from_str (229-351)
  • all_zeros (439-441)
  • from_byte_array (443-445)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (35-41)
bip322/src/lib.rs (2)
  • hash (46-48)
  • verify (54-57)
bip322/validate_unisat_comprehensive.py (1)
bip322/validate_unisat_vector.py (4)
  • parse_bech32_address (15-29)
  • bitcoin_message_hash (31-52)
  • recover_pubkey_from_signature (54-99)
  • main (129-184)
bip322/src/hashing.rs (2)
near-utils/src/digest.rs (3)
  • digest (143-146)
  • tagged (118-118)
  • tagged (122-125)
bip322/src/bitcoin_minimal.rs (1)
  • from_bytes (429-431)
bip322/validate_unisat_vector.py (1)
bip322/validate_unisat_comprehensive.py (3)
  • parse_bech32_address (15-28)
  • recover_pubkey_from_signature (53-179)
  • main (181-229)
bip322/src/transaction.rs (2)
near-utils/src/digest.rs (1)
  • digest (143-146)
bip322/src/bitcoin_minimal.rs (6)
  • new (140-142)
  • new (425-427)
  • new (456-458)
  • all_zeros (439-441)
  • from_bytes (429-431)
  • from_byte_array (443-445)
bip322/src/lib.rs (3)
core/src/payload/multi.rs (2)
  • hash (67-78)
  • verify (85-96)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/bitcoin_minimal.rs (1)
  • from_str (229-351)
bip322/src/signature.rs (5)
bip322/src/transaction.rs (2)
  • create_to_sign (101-141)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (4)
  • from (841-845)
  • new (140-142)
  • new (425-427)
  • new (456-458)
bip322/src/verification.rs (2)
  • validate_pubkey_matches_address (23-33)
  • validate_compressed_pubkey_matches_address (48-81)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (35-41)
near-utils/src/digest.rs (1)
  • digest (143-146)
bip322/src/verification.rs (1)
near-utils/src/digest.rs (1)
  • digest (143-146)
bip322/src/bitcoin_minimal.rs (1)
bip322/src/signature.rs (2)
  • from_str (84-98)
  • from (76-78)
🪛 Ruff (0.12.2)
bip322/validate_unisat_comprehensive.py

8-8: binascii.unhexlify imported but unused

Remove unused import: binascii.unhexlify

(F401)


26-26: Do not use bare except

(E722)


56-56: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


58-58: ecdsa.ecdsa.possible_public_keys_from_signature imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


84-84: bitcoinlib.keys.Key imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


135-135: Local variable point is assigned to but never used

Remove assignment to unused variable point

(F841)


204-204: f-string without any placeholders

Remove extraneous f prefix

(F541)

bip322/validate_unisat_vector.py

8-8: binascii.unhexlify imported but unused

Remove unused import: binascii.unhexlify

(F401)


27-27: Do not use bare except

(E722)


57-57: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


58-58: ecdsa.curves.SECP256k1 imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


59-59: ecdsa.ellipticcurve.Point imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


120-120: Local variable sha256_hash is assigned to but never used

Remove assignment to unused variable sha256_hash

(F841)


154-154: f-string without any placeholders

Remove extraneous f prefix

(F541)


165-165: Local variable recovered_pubkey is assigned to but never used

Remove assignment to unused variable recovered_pubkey

(F841)


169-169: bech32 imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


175-175: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

🪛 GitHub Check: Format
bip322/src/hashing.rs

[warning] 147-147:
Diff in /home/runner/work/intents/intents/bip322/src/hashing.rs


[warning] 4-4:
Diff in /home/runner/work/intents/intents/bip322/src/hashing.rs

bip322/src/lib.rs

[warning] 52-52:
Diff in /home/runner/work/intents/intents/bip322/src/lib.rs


[warning] 44-44:
Diff in /home/runner/work/intents/intents/bip322/src/lib.rs


[warning] 14-14:
Diff in /home/runner/work/intents/intents/bip322/src/lib.rs

bip322/src/signature.rs

[warning] 102-102:
Diff in /home/runner/work/intents/intents/bip322/src/signature.rs

bip322/src/bitcoin_minimal.rs

[warning] 294-294:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 257-257:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 190-190:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs


[warning] 41-41:
Diff in /home/runner/work/intents/intents/bip322/src/bitcoin_minimal.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (8)
near-utils/src/digest.rs (4)

34-70: Hash160 implementation looks solid and aligns with Bitcoin semantics

Uses NEAR host functions for SHA-256 and RIPEMD-160, exposes digest traits correctly, and tests validate behavior. Good addition for BIP-322 address/pubkey flows.


71-107: Double digest wrapper is correct and efficient

Forwarding Update to inner digest and using finalize_fixed for the first pass before hashing again is clean. Generic design keeps it reusable (e.g., DoubleSha256).


109-126: TaggedDigest blanket impl matches tagged hashing spec

Tag hashing as tag_hash || tag_hash || msg is the right construction. Tests confirm expected behavior.


168-181: Nice tests coverage for new utilities

Test cases for Hash160, DoubleSha256, and tagged hashing provide good confidence. No issues spotted.

bip322/src/signature.rs (2)

195-233: Witness parsing with varint and DoS limits looks solid.

Good defensive checks on witness count and item sizes, and clean varint decoding. This should be robust against malformed inputs.


412-432: Recovery-ID handling: confirm env::ecrecover semantics.

You map 27–34 into 0–3 and set the malleability flag to true. Please ensure:

  • NEAR env::ecrecover expects v in 0–3 (not 27/28), and
  • The malleability flag usage aligns with your security requirements.

Do you want me to add targeted unit tests with known vectors (including compressed/uncompressed flags in the header) to assert recovery correctness across these cases?

bip322/src/bitcoin_minimal.rs (2)

230-351: Base58 parsing looks correct and checksums are validated.

P2PKH and P2SH branches are implemented with proper length checks, version checks, and Base58Check checksum verification. Good.


139-147: const fn with Vec is not portable; remove const to avoid compilation issues.

Vec::new() in a const fn isn’t guaranteed to be allowed on stable compilers, and const fn from_stack(Vec<...>) provides no benefit. Use non-const constructors.

-impl TransactionWitness {
-    pub const fn new() -> Self {
-        Self { stack: Vec::new() }
-    }
-
-    pub const fn from_stack(stack: Vec<Vec<u8>>) -> Self {
-        Self { stack }
-    }
-}
+impl TransactionWitness {
+    pub fn new() -> Self {
+        Self { stack: Vec::new() }
+    }
+
+    pub fn from_stack(stack: Vec<Vec<u8>>) -> Self {
+        Self { stack }
+    }
+}

Likely an incorrect or invalid review comment.

Comment on lines 156 to 167
computed_pubkey_hash[0] == witness_program.program.as_slice()
|| computed_pubkey_hash[1] == witness_program.program.as_slice()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix equality in P2WPKH validation (raw recovered pubkey path)

As above, compare slices explicitly to avoid array-to-slice pitfalls.

Apply this diff:

-    computed_pubkey_hash[0] == witness_program.program.as_slice() 
-        || computed_pubkey_hash[1] == witness_program.program.as_slice()
+    &computed_pubkey_hash[0][..] == witness_program.program.as_slice()
+        || &computed_pubkey_hash[1][..] == witness_program.program.as_slice()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
computed_pubkey_hash[0] == witness_program.program.as_slice()
|| computed_pubkey_hash[1] == witness_program.program.as_slice()
}
&computed_pubkey_hash[0][..] == witness_program.program.as_slice()
|| &computed_pubkey_hash[1][..] == witness_program.program.as_slice()
}
🤖 Prompt for AI Agents
In bip322/src/verification.rs around lines 156 to 158, the code compares array
elements to a slice using equality which can cause array-to-slice mismatches;
change the comparisons to compare slices explicitly by calling .as_slice() on
the computed_pubkey_hash entries so the condition becomes
computed_pubkey_hash[0].as_slice() == witness_program.program.as_slice() ||
computed_pubkey_hash[1].as_slice() == witness_program.program.as_slice().

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (3)
bip322/src/signature.rs (2)

373-387: Do not return zero-filled placeholder keys for compressed pubkeys

Fabricating [0u8; 64] on “validation success” is dangerous and can leak fake key material downstream. Return None unless you can produce a real key.

Apply this diff:

             ParsedPublicKey::Compressed(compressed) => {
                 // Validate compressed public key against address
                 if crate::verification::validate_compressed_pubkey_matches_address(
                     compressed, address,
                 ) {
-                    // Validation succeeded, but we cannot provide uncompressed format
-                    // This indicates a successful verification but inability to decompress
-                    // For now, we'll create a placeholder uncompressed key to indicate success
-                    // TODO: Implement proper decompression or change API to accept compressed keys
-                    Some([0u8; 64]) // Placeholder indicating successful validation
+                    // We should not fabricate key bytes.
+                    // TODO: either decompress or change API to accept compressed keys.
+                    None
                 } else {
                     None
                 }
             }

450-473: Fix Bitcoin Signed Message hash: missing CompactSize(prefix) before the prefix

Without writing the CompactSize length of the prefix prior to the prefix, standard wallet signatures won’t verify.

Apply this diff:

         // Create the full message with prefix and length
         let mut full_message = Vec::new();
-        full_message.extend_from_slice(prefix);
+        // Write CompactSize length of the prefix first
+        Self::encode_varint(prefix.len() as u64, &mut full_message);
+        full_message.extend_from_slice(prefix);

         // Add message length as proper varint
         Self::encode_varint(message_bytes.len() as u64, &mut full_message);
bip322/src/bitcoin_minimal.rs (1)

517-595: Consensus encoding uses write(...); switch to write_all(...) to avoid partial writes

Write::write is allowed to perform partial writes. For consensus serialization this is unsafe and can produce truncated encodings depending on the writer.

Apply the pattern below throughout this function (illustrative subset):

-        // Version (4 bytes, little-endian)
-        len += writer.write(&self.version.to_le_bytes())?;
+        // Version (4 bytes, little-endian)
+        writer.write_all(&self.version.to_le_bytes())?;
+        len += 4;
@@
-        if has_witness {
-            len += writer.write(&[0x00])?; // Marker byte
-            len += writer.write(&[0x01])?; // Flag byte
-        }
+        if has_witness {
+            writer.write_all(&[0x00, 0x01])?; // Marker and flag
+            len += 2;
+        }
@@
-            len += writer.write(&input.previous_output.txid.0)?;
-            len += writer.write(&input.previous_output.vout.to_le_bytes())?;
+            writer.write_all(&input.previous_output.txid.0)?;
+            writer.write_all(&input.previous_output.vout.to_le_bytes())?;
+            len += 32 + 4;
@@
-            len += writer.write(&input.script_sig.inner)?;
+            writer.write_all(&input.script_sig.inner)?;
+            len += input.script_sig.inner.len();
@@
-            len += writer.write(&input.sequence.to_le_bytes())?;
+            writer.write_all(&input.sequence.to_le_bytes())?;
+            len += 4;
@@
-            len += writer.write(&output.value.to_le_bytes())?;
+            writer.write_all(&output.value.to_le_bytes())?;
+            len += 8;
@@
-            len += writer.write(&output.script_pubkey.inner)?;
+            writer.write_all(&output.script_pubkey.inner)?;
+            len += output.script_pubkey.inner.len();
@@
-                    len += writer.write(witness_item)?;
+                    writer.write_all(witness_item)?;
+                    len += witness_item.len();
@@
-        len += writer.write(&self.lock_time.to_le_bytes())?;
+        writer.write_all(&self.lock_time.to_le_bytes())?;
+        len += 4;

Alternatively, drop manual len tracking and return Ok(()), since deterministic lengths can be recomputed by callers when needed.

🧹 Nitpick comments (4)
tests/src/utils/crypto.rs (1)

16-17: Prefer borrowing over owning for message parameter in test Signer

Taking message: &str avoids an allocation and matches other signer methods that accept payloads by reference/value. Low impact in tests but cleaner.

bip322/src/signature.rs (1)

84-98: Be explicit about accepted compact formats and recovery IDs

Currently only 65-byte base64 signatures are accepted as “compact”. Many wallets emit DER-encoded signatures. If wallet compatibility is a goal, consider supporting DER + recovery-ID derivation, or clarify that only Bitcoin Core-style 65-byte compact signatures are accepted.

Would you like a follow-up patch to support DER parsing and recovery ID search (0..=3)?

bip322/src/bitcoin_minimal.rs (2)

11-23: Docs out of date: module already supports P2SH and P2WSH

The “MVP Focus” section claims only P2PKH/P2WPKH support, but the code implements P2SH and P2WSH too. Update to reflect current capabilities.


386-416: Bech32 HRP validation: avoid unwrap and use a constant HRP

Minor: replace Hrp::parse("bc").unwrap() with a compile-time parse or checked parse once to avoid potential panics in future refactors.

Example:

// top-level
const MAINNET_HRP: Hrp = Hrp::parse_unchecked("bc");
// ...
if hrp != MAINNET_HRP { return Err(AddressError::InvalidBech32); }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 05f1d14 and d5bc1b8.

⛔ Files ignored due to path filters (1)
  • bip322/unisat-failure.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/hashing.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/src/signature.rs (1 hunks)
  • bip322/src/tests.rs (1 hunks)
  • bip322/src/transaction.rs (1 hunks)
  • bip322/src/verification.rs (1 hunks)
  • near-utils/src/digest.rs (3 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • bip322/src/transaction.rs
  • bip322/src/hashing.rs
  • bip322/src/tests.rs
  • bip322/src/verification.rs
🧰 Additional context used
🧬 Code Graph Analysis (3)
bip322/src/lib.rs (3)
core/src/payload/multi.rs (2)
  • hash (67-78)
  • verify (85-96)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/bitcoin_minimal.rs (1)
  • from_str (227-351)
bip322/src/signature.rs (4)
bip322/src/transaction.rs (2)
  • create_to_sign (101-141)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (5)
  • from (841-845)
  • from_str (227-351)
  • new (139-141)
  • new (425-427)
  • new (456-458)
bip322/src/verification.rs (2)
  • validate_pubkey_matches_address (23-37)
  • validate_compressed_pubkey_matches_address (52-90)
bip322/src/hashing.rs (2)
  • compute_message_hash (61-74)
  • compute_bip322_message_hash (38-44)
bip322/src/bitcoin_minimal.rs (2)
near-utils/src/digest.rs (1)
  • digest (146-149)
bip322/src/signature.rs (2)
  • from_str (84-98)
  • from (76-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Security Audit - report
🔇 Additional comments (9)
near-utils/src/digest.rs (9)

1-4: Imports look correct and scoped appropriately

The selected traits and const sizes are exactly what's used below. No unnecessary imports spotted.


37-70: HASH160 implementation (RIPEMD160(SHA256(data))) is correct and leverages NEAR host functions

The two-pass hashing matches Bitcoin’s HASH160. Using env::{sha256_array,ripemd160_array} aligns with the stated gas-efficiency goal.


74-98: Generic Double<D> wrapper design looks good

  • Update proxies to the inner digest as expected.
  • Output size is correctly inherited from D.
  • Using a fresh default hasher for the second pass mirrors standard double-hash semantics.

Once the chain_update fix is applied (see above), this is solid.


112-123: Tagged hashing API fits BIP-style domain separation

The TaggedDigest trait and default impl via Self::digest(tag) -> prepend tag hash twice are idiomatic and match BIP-340/322 tagged hashing construction.


131-135: DoubleSha256 alias is appropriate and clear

The type alias concisely ties the generic wrapper to the NEAR-backed Sha256.


151-159: HASH160 test correctly mirrors the two-pass construction

The expected value matches RIPEMD160(SHA256(random_bytes)) via the same NEAR host functions.


161-169: Double-SHA256 test validates both passes correctly

The test structure is correct and ensures the wrapper composes two host-function-backed passes.


171-187: Tagged hashing test is accurate

Building expected = SHA256(tag_hash || tag_hash || data) matches the implementation of TaggedDigest::tagged. Good coverage.


124-130: No accidental usages of DoubleSha256::tagged found

I ran a repo-wide search for any calls to DoubleSha256::tagged(...) (and more generic Double<…>::tagged(...)) and found no matches. There’s no evidence that the blanket impl<D: Digest> TaggedDigest for D is being invoked on DoubleSha256 in the current codebase.

No further action needed here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
bip322/src/verification.rs (4)

58-66: Fix array-to-slice comparison and validate P2WPKH witness program format.

Current comparison uses [u8; 20] == &[u8], which is brittle. Also validate segwit v0 20-byte program.

-        Address::P2WPKH { witness_program } => {
-            let computed_hash: [u8; 20] =
-                defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
-            computed_hash == witness_program.program.as_slice()
-        }
+        Address::P2WPKH { witness_program } => {
+            // Validate program: v0 and 20 bytes
+            if witness_program.version != 0 || witness_program.program.len() != 20 {
+                return false;
+            }
+            let computed_hash: [u8; 20] =
+                defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
+            &computed_hash[..] == witness_program.program.as_slice()
+        }

82-88: Fix array-to-slice comparison and validate P2WSH witness program format.

Ensure v0/32-byte program and compare slices.

-        Address::P2WSH { witness_program } => {
+        Address::P2WSH { witness_program } => {
+            if witness_program.version != 0 || witness_program.program.len() != 32 {
+                return false;
+            }
             let pubkey_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
             let witness_script = build_witness_script(&pubkey_hash);
             let computed_script_hash = env::sha256_array(&witness_script);
-            computed_script_hash == witness_program.program.as_slice()
+            &computed_script_hash[..] == witness_program.program.as_slice()
         }

165-167: Fix P2WPKH raw recovered pubkey path: slice comparison and program validation.

Add v0/20-byte check and compare as slices.

-    computed_pubkey_hash[0] == witness_program.program.as_slice()
-        || computed_pubkey_hash[1] == witness_program.program.as_slice()
+    if witness_program.version != 0 || witness_program.program.len() != 20 {
+        return false;
+    }
+    &computed_pubkey_hash[0][..] == witness_program.program.as_slice()
+        || &computed_pubkey_hash[1][..] == witness_program.program.as_slice()

205-223: Fix slice comparisons and add P2WSH format checks (raw pubkey path).

Align with compressed path: validate (v0, 32 bytes) and compare slices.

-    let computed_script_hash = env::sha256_array(&witness_script);
-    if computed_script_hash == witness_program.program.as_slice() {
+    if witness_program.version != 0 || witness_program.program.len() != 32 {
+        return false;
+    }
+    let computed_script_hash = env::sha256_array(&witness_script);
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
@@
-    let computed_script_hash = env::sha256_array(&witness_script);
-    if computed_script_hash == witness_program.program.as_slice() {
+    let computed_script_hash = env::sha256_array(&witness_script);
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
@@
-    let computed_script_hash = env::sha256_array(&witness_script);
-    computed_script_hash == witness_program.program.as_slice()
+    let computed_script_hash = env::sha256_array(&witness_script);
+    &computed_script_hash[..] == witness_program.program.as_slice()
🧹 Nitpick comments (6)
bip322/README.md (1)

75-87: Clarify P2SH support scope (legacy vs nested SegWit).

For P2SH, verification depends on the redeem script, which is not derivable from the address alone. The code path currently cannot infer nested P2SH-P2WPKH from the address and needs data from the signature/witness to compute the correct sighash. Please add a short note in README explaining that:

  • P2SH-P2WPKH is supported via full signatures (redeem script contained within the signature).
  • Legacy P2SH redeem scripts are only supported where the redeem script can be reconstructed (e.g., P2PKH template), otherwise caller must provide it.
bip322/src/hashing.rs (2)

94-107: P2SH with legacy sighash likely incorrect scriptCode.

compute_legacy_sighash() uses to_spend.output[0].script_pubkey as script_code. For P2SH with legacy signing, the scriptCode should be the redeem script, not the P2SH scriptPubKey. If you keep this function generic, add a note/assertion preventing its use for P2SH, or accept an explicit script_code parameter.

-    let script_code = &to_spend.output.first().expect("to_spend should have output").script_pubkey;
+    let script_code = &to_spend
+        .output
+        .first()
+        .expect("to_spend should have output")
+        .script_pubkey;
+    // Note: For P2SH legacy sighash, script_code must be the redeem script (not the P2SH script_pubkey).
+    // Callers should pass the correct script_code via a context-aware API.

102-105: Avoid panics in library code; return errors instead.

The .expect(...) calls will panic on malformed inputs. Prefer returning a Result from the hash computation functions and propagating errors to callers, especially since this can run in contract contexts where panics are costly.

Also applies to: 169-171

bip322/src/tests.rs (3)

296-316: Test name/comment mismatch: not actually testing “wrong witness type”.

The test constructs a P2PKH address with a zeroed compact signature. It doesn’t construct a mismatched witness type. Consider renaming the test or updating the scenario to build a full-format signature with an incompatible witness.

-fn test_signature_verification_wrong_witness_type() {
+fn test_signature_verification_invalid_compact_signature() {

340-361: Misleading test name: length is valid; content is invalid.

65 bytes is the correct length for a compact signature. Either rename the test or provide an invalid-length signature (e.g., 64 bytes) to actually test length handling.

- fn test_signature_verification_invalid_signature_length() {
+ fn test_signature_verification_invalid_signature() {
@@
-    let invalid_signature = [0u8; 65]; // Valid 65-byte signature (but empty, so will fail)
+    // Invalid: wrong size to exercise length validation
+    let invalid_signature = [0u8; 64];

535-576: Strengthen “working vectors” test to fail on unexpected failures.

Currently failures only log and don’t fail the test. If these are meant to be “working”, assert success so regressions are caught. If you want a softer guardrail, at least count failures and assert they’re zero.

-                match payload.verify() {
-                    Some(_pubkey) => {
-                        println!("✓ Working vector {i} verified successfully");
-                    }
-                    None => {
-                        println!(
-                            "✗ Working vector {i} failed verification (might need implementation fixes)"
-                        );
-                        // Don't panic here since we might have implementation issues to fix
-                    }
-                }
+                assert!(
+                    payload.verify().is_some(),
+                    "Working vector {i} failed verification: {}",
+                    vector.description
+                );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d5bc1b8 and 4f6cedc.

📒 Files selected for processing (9)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/hashing.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/src/signature.rs (1 hunks)
  • bip322/src/tests.rs (1 hunks)
  • bip322/src/transaction.rs (1 hunks)
  • bip322/src/verification.rs (1 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/src/utils/crypto.rs
  • bip322/src/transaction.rs
  • bip322/src/lib.rs
  • bip322/src/signature.rs
  • bip322/src/bitcoin_minimal.rs
🧰 Additional context used
🧬 Code Graph Analysis (3)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (2)
  • compute_tx_id (151-157)
  • create_to_sign (101-137)
bip322/src/bitcoin_minimal.rs (6)
  • new (144-146)
  • new (436-438)
  • new (467-469)
  • from_str (232-356)
  • all_zeros (450-452)
  • from_byte_array (454-456)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (38-44)
bip322/src/lib.rs (2)
  • hash (45-48)
  • verify (54-60)
bip322/src/hashing.rs (3)
near-utils/src/digest.rs (3)
  • digest (146-149)
  • tagged (121-121)
  • tagged (125-128)
bip322/src/signature.rs (1)
  • compute_message_hash (434-450)
bip322/src/bitcoin_minimal.rs (1)
  • from_bytes (440-442)
bip322/src/verification.rs (1)
near-utils/src/digest.rs (1)
  • digest (146-149)
🪛 LanguageTool
bip322/README.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...EAR blockchain ecosystem. ## 🎯 Purpose This module provides **complete BIP-322 s...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ... using only NEAR SDK host functions - 📋 Wide Coverage: Supports most major Bi...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...types (P2PKH, P2SH, P2WPKH, P2WSH) - ⚡ Gas Optimized: Minimal gas consumption...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...gh efficient NEAR SDK integration - 🔒 Security Focused: Comprehensive valida...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...ation with proper error handling - 🧪 Well Tested: Extensive test suite with ...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...fficial BIP-322 reference vectors ## 🏗️ Architecture ### Core Components - **`...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...2 reference vectors ## 🏗️ Architecture ### Core Components - lib.rs: Main `Si...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...nd SignedPayload trait implementations - signature.rs: BIP-322 signature parsing and verifica...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...signature parsing and verification logic - bitcoin_minimal.rs: Minimal Bitcoin types optimized for BI...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...P-322 (transactions, addresses, scripts) - hashing.rs: BIP-322 message hash computation with ...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...h computation with proper tagged hashing - transaction.rs: BIP-322 "to_spend" and "to_sign" trans...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - verification.rs: Address validation and public key reco...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...se64 signature decoding ## 🚀 Usage rust use defuse_bip322::SignedBip322Payload; use defuse_crypto::SignedPayload; // Parse and verify a BIP-322 signature let payload = SignedBip322Payload { address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l".parse()?, message: "Hello Bitcoin!".to_string(), signature: "AkcwRAIgeGl4sSPd7zEIvhxdN8GgP4vgSqA8TdyPMeIpCF4gqgE4AiBsjQd0D1OFxdnHQPNOI1YdGlBD6kEOGRnHhcAkHnxUcAH=".parse()?, }; // Verify signature and extract public key if let Some(public_key) = payload.verify() { println!("✅ Valid BIP-322 signature!"); println!("🔑 Public key: {:?}", public_key); } else { println!("❌ Invalid signature"); } ``` ## 📊 Supported Features ### ✅ Address Type...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...ature"); } ``` ## 📊 Supported Features ### ✅ Address Types (Mainnet Only) | Type | ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...) | Type | Format | Example | Support | |------|--------|---------|---------| | ...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ... | |------|--------|---------|---------| | P2PKH | Legacy addresses starting ...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...Gefi2DMPTfTL5SLmv7DivfNa` | ✅ Complete | | P2SH | Script addresses starting w...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...iAE6uzMj2ZifT9YgRrkSgzQX` | ✅ Complete | | P2WPKH | Bech32 addresses starting...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...rlzms0wvx3gsqjx7vavgkx0l` | ✅ Complete | | P2WSH | Bech32 script addresses (3...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...: 65-byte compact format (P2PKH, P2WPKH) - Full Signatures: Complete BIP-322 witn...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...P-322 witness stack format (P2SH, P2WSH) - Automatic Detection: Parses both forma...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...-signed-message" tagged hash computation - Transaction Structure: Correct "to_spe...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - Witness Handling: Complete witness sta...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...ete witness stack parsing and validation - Address Validation: Full address forma...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...n ## 🔍 Discovered Issues & Limitations During implementation and testing, severa...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...e not currently supported. Details: - P2TR uses Taproot (BIP-341) with differe...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...s compressed 33-byte keys. Details: - NEAR SDK ecrecover returns 64-byte unc...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...hem. Implementation of the decompression inside contract is computationally inten...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...es not provide a way to uncompress keys. - See TODO at `bip322/src/signature.rs:384...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...signature.rs:384` Current Behavior: - Compressed key validation works correctl...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...tion Results** (see validation scripts): 1. Python Verification: External Bitcoin ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...to the given address Evidence: See unisat-failure.png - screenshot showing verif...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ... appears to be invalid, possibly due to: - Incorrect signature generation by the wa...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...rrect signature generation by the wallet - Wrong message format during signing - Co...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...et - Wrong message format during signing - Copy/paste errors in the test vector **...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...ted as expecting failure. ## 🧪 Testing The module includes comprehensive testing...

(QB_NEW_EN)


[grammar] ~153-~153: There might be a mistake here.
Context: ...g, message hashing, transaction building - Integration Tests: End-to-end signatur...

(QB_NEW_EN)


[grammar] ~154-~154: There might be a mistake here.
Context: ...-to-end signature verification workflows - Reference Vectors: Official BIP-322 te...

(QB_NEW_EN)


[grammar] ~155-~155: There might be a mistake here.
Context: ...-322 test vectors from the specification - Edge Cases: Invalid signatures, malfor...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: .../29 tests passing** (98.6% success rate) - 1 test ignored (invalid Unisat vector) -...

(QB_NEW_EN)


[grammar] ~161-~161: There might be a mistake here.
Context: ...- 1 test ignored (invalid Unisat vector) - All official BIP-322 reference vectors p...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...valid cases ## 📄 Standards Compliance - ✅ BIP-322: Complete implementation of G...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...ntation of Generic Signed Message Format - ✅ BIP-143: Segwit transaction digest a...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...3**: Segwit transaction digest algorithm - ✅ Base58Check: Legacy address encoding...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...*: Legacy address encoding (P2PKH, P2SH) - ✅ Bech32: Segwit address encoding (P2W...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...oding (P2WPKH, P2WSH) ## 🤝 Integration This module integrates seamlessly with th...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (5)
bip322/README.md (1)

66-74: P2WSH “Complete” claim is inconsistent with code (hashing panics for P2WSH).

In the code, compute_segwit_v0_sighash() explicitly panics for P2WSH because the witness script is required and not derivable from the address. Either implement a P2WSH path that accepts/provides the witness script to the hasher, or adjust the README to state “Partial” support with a clear note that the witness script must be supplied by the caller (e.g., via the full signature).

Suggested README tweak:

-| **P2WSH** | Bech32 script addresses (32-byte) | `bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3` | ✅ Complete |
+| **P2WSH** | Bech32 script addresses (32-byte) | `bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3` | ⚠️ Partial (requires witness script) |

Would you like me to implement a minimal API change to pass the witness script into the hasher and update the verification path accordingly?

bip322/src/hashing.rs (1)

124-159: P2WSH path currently panics; provide a non-panicking API or accept witness script.

The P2WSH arm panics because the witness script is not derivable from the address. If README claims P2WSH support, you should:

  • Accept the witness script (from the full signature) and construct scriptCode accordingly, or
  • Return a Result and surface a clear Unsupported/Invalid error instead of panicking.

Minimal patch to return a Result (propagate up), if you opt not to extend the context now:

-pub fn compute_segwit_v0_sighash(
+pub fn compute_segwit_v0_sighash(
     to_spend: &Transaction,
     to_sign: &Transaction,
     address: &Address,
-) -> near_sdk::CryptoHash {
+) -> near_sdk::CryptoHash {
     // ...
-            Address::P2WSH { .. } => {
-                panic!(
-                    "compute_segwit_v0_sighash: P2WSH requires the witness script (not derivable from address)"
-                )
-            }
+            Address::P2WSH { .. } => {
+                // TODO: Plumb witness script via an extended API; for now, unreachable
+                unreachable!("P2WSH requires witness script; use the extended API passing scriptCode");
+            }

Preferably, adopt the context-based API from the previous comment to truly support P2WSH.

bip322/src/verification.rs (1)

169-195: P2SH assumptions differ between raw and compressed paths; document or generalize.

  • validate_p2sh_address() (raw path) assumes P2SH-P2PKH (redeem script = P2PKH template).
  • validate_compressed_pubkey_matches_address() (compressed path) assumes P2SH-P2WPKH (redeem script = v0 P2WPKH program).

This inconsistency can confuse future maintenance and lead to silent failures for other P2SH scripts. Either:

  • Explicitly document accepted P2SH variants and reject others, or
  • Accept the redeem script (or witness program) from the signature and validate generically.
bip322/src/tests.rs (2)

439-446: Vector expectation contradicts documented Unisat failure.

This vector is marked invalid in README and in ignored tests, but here it’s expected to verify. Flip expected_verification to false or exclude it from “working” vectors.

-                expected_verification: true,
-                description: "P2WPKH JSON message (working example)",
+                expected_verification: false,
+                description: "P2WPKH JSON message (Unisat vector; known invalid)",

730-742: These reference tests implicitly require correct P2SH-P2WPKH/P2WSH handling in hashing.

Given the hasher currently selects legacy sighash for all P2SH and panics for P2WSH, these tests will fail unless the verification path bypasses compute_message_hash() or passes the correct scriptCode. Ensure signature.rs uses a context-aware hasher (see hashing.rs comment) or otherwise provides the necessary script data.

Also applies to: 744-757, 759-772, 774-787, 789-802

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (9)
bip322/README.md (1)

11-16: Clarify dependency claim to avoid misleading readers

"Zero-dependency cryptography" can be interpreted as “no dependencies,” yet the crate uses bs58, bech32, and base64 (as correctly listed below). Recommend rewording to “No external crypto libraries” and call out minimal, non-crypto encoding deps.

Apply this diff:

- - **🛡️ Production Ready**: Zero-dependency cryptography using only NEAR SDK host functions
+ - **🛡️ Production Ready**: No external crypto libraries — all cryptography uses NEAR SDK host functions
bip322/src/verification.rs (4)

156-167: Validate witness program format and compare slices in P2WPKH (recovered key path)

Add version/length checks and avoid array-to-slice equality pitfalls.

Apply this diff:

 fn validate_p2wpkh_address(
     recovered_pubkey: &[u8; 64],
     witness_program: &crate::bitcoin_minimal::WitnessProgram,
 ) -> bool {
+    // Enforce segwit v0 P2WPKH (20-byte program)
+    if witness_program.version != 0 || witness_program.program.len() != 20 {
+        return false;
+    }
     // P2WPKH addresses always use compressed public keys, so two possibilities,
     // depending on the y coordinate parity
     let computed_pubkey_hash = compute_pubkey_hash160_all(recovered_pubkey, true);
 
-    computed_pubkey_hash[0] == witness_program.program.as_slice()
-        || computed_pubkey_hash[1] == witness_program.program.as_slice()
+    &computed_pubkey_hash[0][..] == witness_program.program.as_slice()
+        || &computed_pubkey_hash[1][..] == witness_program.program.as_slice()
 }

197-223: Validate witness program format and compare slices in P2WSH (recovered key path)

Ensure v0/32-byte program and slice-to-slice comparisons to prevent accidental matches.

Apply this diff:

 fn validate_p2wsh_address(
     recovered_pubkey: &[u8; 64],
     witness_program: &crate::bitcoin_minimal::WitnessProgram,
 ) -> bool {
+    // Enforce segwit v0 P2WSH (32-byte program)
+    if witness_program.version != 0 || witness_program.program.len() != 32 {
+        return false;
+    }
     // Try uncompressed first
     let pubkey_hash = compute_pubkey_hash160_all(recovered_pubkey, false);
     let witness_script = build_witness_script(&pubkey_hash[0]);
     let computed_script_hash = env::sha256_array(&witness_script);
 
-    if computed_script_hash == witness_program.program.as_slice() {
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
 
     // Try compressed next
     let pubkey_hash = compute_pubkey_hash160_all(recovered_pubkey, true);
 
     let witness_script = build_witness_script(&pubkey_hash[0]);
     let computed_script_hash = env::sha256_array(&witness_script);
-    if computed_script_hash == witness_program.program.as_slice() {
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
 
     let witness_script = build_witness_script(&pubkey_hash[1]);
     let computed_script_hash = env::sha256_array(&witness_script);
-    computed_script_hash == witness_program.program.as_slice()
+    &computed_script_hash[..] == witness_program.program.as_slice()
 }

101-121: P2PKH/uncompressed hash160 is wrong (missing 0x04 prefix)

Bitcoin’s uncompressed pubkey serialization is 65 bytes: 0x04 || X(32) || Y(32). Hashing the bare 64-byte X||Y yields the wrong hash160 and breaks P2PKH matches for uncompressed keys.

Apply this diff:

 fn compute_pubkey_hash160_all(raw_pubkey: &[u8; 64], compressed: bool) -> Vec<[u8; 20]> {
@@
-    vec![defuse_near_utils::digest::Hash160::digest(raw_pubkey).into()]
+    // Uncompressed serialization: 0x04 || X(32) || Y(32)
+    let mut uncompressed = Vec::with_capacity(65);
+    uncompressed.push(0x04);
+    uncompressed.extend_from_slice(raw_pubkey);
+    vec![defuse_near_utils::digest::Hash160::digest(&uncompressed).into()]
 }

56-89: Strengthen P2WPKH/P2WSH checks for compressed key path (validate version/length; compare slices)

Currently compares arrays to slices directly and doesn’t validate witness program version/length. Add explicit checks and slice-to-slice equality.

Apply this diff:

 pub fn validate_compressed_pubkey_matches_address(
     compressed_pubkey: &[u8; 33],
     address: &Address,
 ) -> bool {
     match address {
         Address::P2PKH { pubkey_hash } => {
             let computed_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
-            computed_hash == *pubkey_hash
+            computed_hash == *pubkey_hash
         }
         Address::P2WPKH { witness_program } => {
+            // Enforce segwit v0 P2WPKH format (20-byte program)
+            if witness_program.version != 0 || witness_program.program.len() != 20 {
+                return false;
+            }
             let computed_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
-            computed_hash == witness_program.program.as_slice()
+            &computed_hash[..] == witness_program.program.as_slice()
         }
         Address::P2SH { script_hash } => {
             let pubkey_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
@@
             let computed_script_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(&witness_program).into();
             computed_script_hash == *script_hash
         }
         Address::P2WSH { witness_program } => {
+            // Enforce segwit v0 P2WSH format (32-byte program)
+            if witness_program.version != 0 || witness_program.program.len() != 32 {
+                return false;
+            }
             let pubkey_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
             let witness_script = build_witness_script(&pubkey_hash);
             let computed_script_hash = env::sha256_array(&witness_script);
-            computed_script_hash == witness_program.program.as_slice()
+            &computed_script_hash[..] == witness_program.program.as_slice()
         }
     }
 }
bip322/src/signature.rs (3)

38-45: Doc mismatch: Compact uses classic Bitcoin message hashing, not BIP-340 tagged hash

Implementation hashes using “Bitcoin Signed Message” double-SHA256, not BIP-340 tagged hash. Update docs for accuracy.

Apply this diff:

     /// Simple/Compact signature format (65 bytes: recovery + r + s).
     ///
-    /// This is the standard Bitcoin message signing format used by most wallets.
-    /// For BIP-322 simple signatures, the message is hashed directly with BIP-340
-    /// tagged hash, not through transaction construction.
+    /// This is the standard Bitcoin message signing format used by most wallets.
+    /// For compact signatures, the message is hashed using the classic Bitcoin
+    /// Signed Message format (double SHA256 over
+    /// CompactSize(len(prefix)) + prefix + CompactSize(message.len()) + message),
+    /// not through transaction construction.

452-478: Bitcoin Signed Message hash missing CompactSize length of the prefix (signatures won’t verify)

The hash must include CompactSize(len("Bitcoin Signed Message:\n")) before the prefix. Without it, standard compact signatures from wallets won’t verify.

Apply this diff:

     /// Compute standard Bitcoin message signing hash.
     ///
     /// This follows the Bitcoin Core format:
-    /// Hash = SHA256(SHA256("Bitcoin Signed Message:\n" + varint(message.len()) + message))
+    /// Hash = SHA256(SHA256(CompactSize(len(prefix)) + prefix
+    ///     + CompactSize(message.len()) + message))
     fn compute_bitcoin_message_hash(message: &str) -> [u8; 32] {
         use defuse_near_utils::digest::DoubleSha256;
         use digest::Digest;
 
         // Bitcoin message signing format
         let prefix = b"Bitcoin Signed Message:\n";
         let message_bytes = message.as_bytes();
 
         // Create the full message with prefix and length
         let mut full_message = Vec::new();
-        full_message.extend_from_slice(prefix);
+        // Add CompactSize(len(prefix)) followed by the prefix
+        Self::encode_varint(
+            u64::try_from(prefix.len()).unwrap_or(0),
+            &mut full_message,
+        );
+        full_message.extend_from_slice(prefix);
 
         // Add message length as proper varint
         Self::encode_varint(
             u64::try_from(message_bytes.len()).unwrap_or(0),
             &mut full_message,
         );
 
         full_message.extend_from_slice(message_bytes);
 
         // Double SHA256 hash
         DoubleSha256::digest(&full_message).into()
     }

374-386: Do not return zero-filled “placeholder” keys; remove fabricated key material

Returning [0u8; 64] on success for compressed keys is unsafe and can be misused as valid key material. Either decompress or return None; consider evolving the API to return a typed enum later.

Apply this diff:

             ParsedPublicKey::Compressed(compressed) => {
                 // Validate compressed public key against address
                 if crate::verification::validate_compressed_pubkey_matches_address(
                     compressed, address,
                 ) {
-                    // Validation succeeded, but we cannot provide uncompressed format
-                    // This indicates a successful verification but inability to decompress
-                    // For now, we'll create a placeholder uncompressed key to indicate success
-                    // TODO: Implement proper decompression or change API to accept compressed keys
-                    Some([0u8; 64]) // Placeholder indicating successful validation
+                    // Validation succeeded, but we cannot provide uncompressed format.
+                    // TODO: Implement decompression or change API to return a typed enum
+                    None
                 } else {
                     None
                 }
             }

Note: This will impact tests that currently expect Some(..) for compressed keys. If you prefer, I can draft a follow-up PR to introduce:

  • RecoveredKey enum { Uncompressed([u8;64]), Compressed([u8;33]) }
  • Wire it through SignedPayload::PublicKey.
bip322/src/bitcoin_minimal.rs (1)

527-605: Use write_all and deterministic length accounting in consensus encoding

Write can be partial; consensus serialization should use write_all and explicit byte counts. Prevents truncated encodings with non-buffered writers.

Apply this diff pattern throughout the function:

-        // Version (4 bytes, little-endian)
-        len += writer.write(&self.version.to_le_bytes())?;
+        // Version (4 bytes, little-endian)
+        writer.write_all(&self.version.to_le_bytes())?;
+        len += 4;
@@
-        if has_witness {
-            len += writer.write(&[0x00])?; // Marker byte
-            len += writer.write(&[0x01])?; // Flag byte
-        }
+        if has_witness {
+            writer.write_all(&[0x00, 0x01])?; // Marker + flag
+            len += 2;
+        }
@@
-            len += writer.write(&input.previous_output.txid.0)?;
-            len += writer.write(&input.previous_output.vout.to_le_bytes())?;
+            writer.write_all(&input.previous_output.txid.0)?;
+            writer.write_all(&input.previous_output.vout.to_le_bytes())?;
+            len += 32 + 4;
@@
-            len += writer.write(&input.script_sig.inner)?;
+            writer.write_all(&input.script_sig.inner)?;
+            len += input.script_sig.inner.len();
@@
-            len += writer.write(&input.sequence.to_le_bytes())?;
+            writer.write_all(&input.sequence.to_le_bytes())?;
+            len += 4;
@@
-            len += writer.write(&output.value.to_le_bytes())?;
+            writer.write_all(&output.value.to_le_bytes())?;
+            len += 8;
@@
-            len += writer.write(&output.script_pubkey.inner)?;
+            writer.write_all(&output.script_pubkey.inner)?;
+            len += output.script_pubkey.inner.len();
@@
-                    len += writer.write(witness_item)?;
+                    writer.write_all(witness_item)?;
+                    len += witness_item.len();
@@
-        len += writer.write(&self.lock_time.to_le_bytes())?;
+        writer.write_all(&self.lock_time.to_le_bytes())?;
+        len += 4;

Optional: drop len tracking and return Ok(()) — callers rarely need the byte count.


</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>bip322/src/tests.rs (1)</summary><blockquote>

`340-361`: **Test name/message are misleading (length is valid; content is invalid)**

This test feeds a 65-byte signature (valid compact length) filled with zeros. The failure is due to invalid signature data, not length. Rename for clarity to avoid confusion when triaging failures.

Apply this diff:

```diff
-    fn test_signature_verification_invalid_signature_length() {
+    fn test_signature_verification_invalid_signature() {
@@
-        let invalid_signature = [0u8; 65]; // Valid 65-byte signature (but empty, so will fail)
+        let invalid_signature = [0u8; 65]; // Valid length, invalid content (all zeros), should fail
@@
-            "Invalid signature length should fail verification"
+            "Invalid signature should fail verification"
bip322/src/bitcoin_minimal.rs (1)

13-17: Docs out of sync with implementation (P2SH/P2WSH are already supported)

Design principles say “Supports only P2PKH and P2WPKH,” but this module implements P2SH and P2WSH too. Update to reflect current scope.

Apply this diff:

-//! 3. **MVP Focus**: Supports only P2PKH and P2WPKH for Phase 2-3
+//! 3. **MVP Focus**: Supports P2PKH, P2SH, P2WPKH, and P2WSH (mainnet)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4f6cedc and 4371d67.

📒 Files selected for processing (9)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/hashing.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/src/signature.rs (1 hunks)
  • bip322/src/tests.rs (1 hunks)
  • bip322/src/transaction.rs (1 hunks)
  • bip322/src/verification.rs (1 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • bip322/src/transaction.rs
  • tests/src/utils/crypto.rs
  • bip322/src/hashing.rs
  • bip322/src/lib.rs
🧰 Additional context used
🧬 Code Graph Analysis (4)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (2)
  • compute_tx_id (151-157)
  • create_to_sign (101-137)
bip322/src/bitcoin_minimal.rs (5)
  • new (144-146)
  • new (436-438)
  • new (467-469)
  • from_str (232-356)
  • from_byte_array (454-456)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (38-44)
bip322/src/lib.rs (2)
  • hash (45-48)
  • verify (54-60)
bip322/src/signature.rs (4)
bip322/src/transaction.rs (2)
  • create_to_sign (101-137)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (4)
  • from (851-855)
  • new (144-146)
  • new (436-438)
  • new (467-469)
bip322/src/verification.rs (2)
  • validate_pubkey_matches_address (23-37)
  • validate_compressed_pubkey_matches_address (52-90)
bip322/src/hashing.rs (2)
  • compute_message_hash (61-74)
  • compute_bip322_message_hash (38-44)
bip322/src/verification.rs (1)
near-utils/src/digest.rs (1)
  • digest (146-149)
bip322/src/bitcoin_minimal.rs (2)
near-utils/src/digest.rs (1)
  • digest (146-149)
bip322/src/signature.rs (2)
  • from_str (84-98)
  • from (76-78)
🪛 LanguageTool
bip322/README.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...EAR blockchain ecosystem. ## 🎯 Purpose This module provides **complete BIP-322 s...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ... using only NEAR SDK host functions - 📋 Wide Coverage: Supports most major Bi...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...types (P2PKH, P2SH, P2WPKH, P2WSH) - ⚡ Gas Optimized: Minimal gas consumption...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...gh efficient NEAR SDK integration - 🔒 Security Focused: Comprehensive valida...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...ation with proper error handling - 🧪 Well Tested: Extensive test suite with ...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...fficial BIP-322 reference vectors ## 🏗️ Architecture ### Core Components - **`...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...2 reference vectors ## 🏗️ Architecture ### Core Components - lib.rs: Main `Si...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...nd SignedPayload trait implementations - signature.rs: BIP-322 signature parsing and verifica...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...signature parsing and verification logic - bitcoin_minimal.rs: Minimal Bitcoin types optimized for BI...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...P-322 (transactions, addresses, scripts) - hashing.rs: BIP-322 message hash computation with ...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...h computation with proper tagged hashing - transaction.rs: BIP-322 "to_spend" and "to_sign" trans...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - verification.rs: Address validation and public key reco...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...se64 signature decoding ## 🚀 Usage rust use defuse_bip322::SignedBip322Payload; use defuse_crypto::SignedPayload; // Parse and verify a BIP-322 signature let payload = SignedBip322Payload { address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l".parse()?, message: "Hello Bitcoin!".to_string(), signature: "AkcwRAIgeGl4sSPd7zEIvhxdN8GgP4vgSqA8TdyPMeIpCF4gqgE4AiBsjQd0D1OFxdnHQPNOI1YdGlBD6kEOGRnHhcAkHnxUcAH=".parse()?, }; // Verify signature and extract public key if let Some(public_key) = payload.verify() { println!("✅ Valid BIP-322 signature!"); println!("🔑 Public key: {:?}", public_key); } else { println!("❌ Invalid signature"); } ``` ## 📊 Supported Features ### ✅ Address Type...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...ature"); } ``` ## 📊 Supported Features ### ✅ Address Types (Mainnet Only) | Type | ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...) | Type | Format | Example | Support | |------|--------|---------|---------| | ...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ... | |------|--------|---------|---------| | P2PKH | Legacy addresses starting ...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...Gefi2DMPTfTL5SLmv7DivfNa` | ✅ Complete | | P2SH | Script addresses starting w...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...iAE6uzMj2ZifT9YgRrkSgzQX` | ✅ Complete | | P2WPKH | Bech32 addresses starting...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...rlzms0wvx3gsqjx7vavgkx0l` | ✅ Complete | | P2WSH | Bech32 script addresses (3...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...: 65-byte compact format (P2PKH, P2WPKH) - Full Signatures: Complete BIP-322 witn...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...P-322 witness stack format (P2SH, P2WSH) - Automatic Detection: Parses both forma...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...-signed-message" tagged hash computation - Transaction Structure: Correct "to_spe...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - Witness Handling: Complete witness sta...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...ete witness stack parsing and validation - Address Validation: Full address forma...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...n ## 🔍 Discovered Issues & Limitations During implementation and testing, severa...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...e not currently supported. Details: - P2TR uses Taproot (BIP-341) with differe...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...s compressed 33-byte keys. Details: - NEAR SDK ecrecover returns 64-byte unc...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...hem. Implementation of the decompression inside contract is computationally inten...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...es not provide a way to uncompress keys. - See TODO at `bip322/src/signature.rs:384...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...signature.rs:384` Current Behavior: - Compressed key validation works correctl...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...tion Results** (see validation scripts): 1. Python Verification: External Bitcoin ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...to the given address Evidence: See unisat-failure.png - screenshot showing verif...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ... appears to be invalid, possibly due to: - Incorrect signature generation by the wa...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...rrect signature generation by the wallet - Wrong message format during signing - Co...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...et - Wrong message format during signing - Copy/paste errors in the test vector **...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...ted as expecting failure. ## 🧪 Testing The module includes comprehensive testing...

(QB_NEW_EN)


[grammar] ~153-~153: There might be a mistake here.
Context: ...g, message hashing, transaction building - Integration Tests: End-to-end signatur...

(QB_NEW_EN)


[grammar] ~154-~154: There might be a mistake here.
Context: ...-to-end signature verification workflows - Reference Vectors: Official BIP-322 te...

(QB_NEW_EN)


[grammar] ~155-~155: There might be a mistake here.
Context: ...-322 test vectors from the specification - Edge Cases: Invalid signatures, malfor...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: .../29 tests passing** (98.6% success rate) - 1 test ignored (invalid Unisat vector) -...

(QB_NEW_EN)


[grammar] ~161-~161: There might be a mistake here.
Context: ...- 1 test ignored (invalid Unisat vector) - All official BIP-322 reference vectors p...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...valid cases ## 📄 Standards Compliance - ✅ BIP-322: Complete implementation of G...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...ntation of Generic Signed Message Format - ✅ BIP-143: Segwit transaction digest a...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...3**: Segwit transaction digest algorithm - ✅ Base58Check: Legacy address encoding...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...*: Legacy address encoding (P2PKH, P2SH) - ✅ Bech32: Segwit address encoding (P2W...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...oding (P2WPKH, P2WSH) ## 🤝 Integration This module integrates seamlessly with th...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Security Audit - report

Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (12)
bip322/README.md (1)

36-40: Dependencies section accurately reflects minimal non-crypto deps. LGTM.

Good call-out separating NEAR-hosted crypto from minimal address/encoding crates. This resolves the earlier ambiguity around “zero external dependencies.”

bip322/validate_unisat_vector.py (1)

31-52: Fix Bitcoin signed-message hash: missing varint(len(prefix)).

Per the Bitcoin “signmessage” format: hash = SHA256(SHA256(varint(len(prefix)) || prefix || varint(len(msg)) || msg)). The current code omits varint(len(prefix)), producing a non-standard hash.

Apply this diff:

@@
-    # Create varint length encoding
-    msg_len = len(message_bytes)
-    if msg_len < 253:
-        len_bytes = bytes([msg_len])
-    elif msg_len <= 0xFFFF:
-        len_bytes = bytes([0xFD]) + msg_len.to_bytes(2, 'little')
-    elif msg_len <= 0xFFFFFFFF:
-        len_bytes = bytes([0xFE]) + msg_len.to_bytes(4, 'little')
-    else:
-        len_bytes = bytes([0xFF]) + msg_len.to_bytes(8, 'little')
+    # Create varint length encoding for prefix and message
+    prefix_len = len(prefix)
+    if prefix_len < 253:
+        prefix_len_bytes = bytes([prefix_len])
+    elif prefix_len <= 0xFFFF:
+        prefix_len_bytes = bytes([0xFD]) + prefix_len.to_bytes(2, 'little')
+    elif prefix_len <= 0xFFFFFFFF:
+        prefix_len_bytes = bytes([0xFE]) + prefix_len.to_bytes(4, 'little')
+    else:
+        prefix_len_bytes = bytes([0xFF]) + prefix_len.to_bytes(8, 'little')
+
+    msg_len = len(message_bytes)
+    if msg_len < 253:
+        len_bytes = bytes([msg_len])
+    elif msg_len <= 0xFFFF:
+        len_bytes = bytes([0xFD]) + msg_len.to_bytes(2, 'little')
+    elif msg_len <= 0xFFFFFFFF:
+        len_bytes = bytes([0xFE]) + msg_len.to_bytes(4, 'little')
+    else:
+        len_bytes = bytes([0xFF]) + msg_len.to_bytes(8, 'little')
@@
-    full_message = prefix + len_bytes + message_bytes
+    full_message = prefix_len_bytes + prefix + len_bytes + message_bytes
bip322/validate_unisat_comprehensive.py (1)

30-51: Fix Bitcoin signed-message hash: missing varint(len(prefix)).

Same issue as the lightweight script. Include varint-encoded prefix length before the prefix.

Apply this diff:

@@
-    # Create varint length encoding
-    msg_len = len(message_bytes)
-    if msg_len < 253:
-        len_bytes = bytes([msg_len])
-    elif msg_len <= 0xFFFF:
-        len_bytes = bytes([0xFD]) + msg_len.to_bytes(2, 'little')
-    elif msg_len <= 0xFFFFFFFF:
-        len_bytes = bytes([0xFE]) + msg_len.to_bytes(4, 'little')
-    else:
-        len_bytes = bytes([0xFF]) + msg_len.to_bytes(8, 'little')
+    # Create varint length encoding for prefix and message
+    prefix_len = len(prefix)
+    if prefix_len < 253:
+        prefix_len_bytes = bytes([prefix_len])
+    elif prefix_len <= 0xFFFF:
+        prefix_len_bytes = bytes([0xFD]) + prefix_len.to_bytes(2, 'little')
+    elif prefix_len <= 0xFFFFFFFF:
+        prefix_len_bytes = bytes([0xFE]) + prefix_len.to_bytes(4, 'little')
+    else:
+        prefix_len_bytes = bytes([0xFF]) + prefix_len.to_bytes(8, 'little')
+
+    msg_len = len(message_bytes)
+    if msg_len < 253:
+        len_bytes = bytes([msg_len])
+    elif msg_len <= 0xFFFF:
+        len_bytes = bytes([0xFD]) + msg_len.to_bytes(2, 'little')
+    elif msg_len <= 0xFFFFFFFF:
+        len_bytes = bytes([0xFE]) + msg_len.to_bytes(4, 'little')
+    else:
+        len_bytes = bytes([0xFF]) + msg_len.to_bytes(8, 'little')
@@
-    full_message = prefix + len_bytes + message_bytes
+    full_message = prefix_len_bytes + prefix + len_bytes + message_bytes
near-utils/src/digest.rs (1)

103-107: Compile fix: use chain_update instead of chain (digest 0.10).

digest::Digest provides chain_update, not chain. As written, this won’t compile.

     fn finalize_into(self, out: &mut digest::Output<Self>) {
-        D::default()
-            .chain(self.0.finalize_fixed())
-            .finalize_into(out);
+        D::default()
+            .chain_update(self.0.finalize_fixed())
+            .finalize_into(out);
     }
bip322/src/verification.rs (5)

101-121: Incorrect Hash160 for uncompressed pubkeys (missing 0x04 prefix).

Bitcoin uncompressed pubkeys are serialized as 65 bytes: 0x04 || X || Y. Hashing raw 64 bytes will not match P2PKH addresses.

 fn compute_pubkey_hash160_all(raw_pubkey: &[u8; 64], compressed: bool) -> Vec<[u8; 20]> {
@@
-    vec![defuse_near_utils::digest::Hash160::digest(raw_pubkey).into()]
+    // Uncompressed serialization: 0x04 || X || Y
+    let mut uncompressed = Vec::with_capacity(65);
+    uncompressed.push(0x04);
+    uncompressed.extend_from_slice(raw_pubkey);
+    vec![defuse_near_utils::digest::Hash160::digest(&uncompressed).into()]
 }

56-66: Fix slice equality and validate P2WPKH program format.

Compare slices explicitly and enforce witness version 0 with 20-byte program length.

-        Address::P2WPKH { witness_program } => {
-            let computed_hash: [u8; 20] =
-                defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
-            computed_hash == witness_program.program.as_slice()
-        }
+        Address::P2WPKH { witness_program } => {
+            // v0 and 20-byte program
+            if witness_program.version != 0 || witness_program.program.len() != 20 {
+                return false;
+            }
+            let computed_hash: [u8; 20] =
+                defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
+            &computed_hash[..] == witness_program.program.as_slice()
+        }

82-88: Fix slice equality and validate P2WSH program format.

As above, enforce v0 and 32-byte program and compare slices.

-        Address::P2WSH { witness_program } => {
+        Address::P2WSH { witness_program } => {
+            if witness_program.version != 0 || witness_program.program.len() != 32 {
+                return false;
+            }
             let pubkey_hash: [u8; 20] =
                 defuse_near_utils::digest::Hash160::digest(compressed_pubkey).into();
             let witness_script = build_witness_script(&pubkey_hash);
             let computed_script_hash = env::sha256_array(&witness_script);
-            computed_script_hash == witness_program.program.as_slice()
+            &computed_script_hash[..] == witness_program.program.as_slice()
         }

156-167: P2WPKH: enforce witness format and fix equality.

Validate v0/20-bytes and compare slices.

-    let computed_pubkey_hash = compute_pubkey_hash160_all(recovered_pubkey, true);
-
-    computed_pubkey_hash[0] == witness_program.program.as_slice()
-        || computed_pubkey_hash[1] == witness_program.program.as_slice()
+    if witness_program.version != 0 || witness_program.program.len() != 20 {
+        return false;
+    }
+    let computed_pubkey_hash = compute_pubkey_hash160_all(recovered_pubkey, true);
+    &computed_pubkey_hash[0][..] == witness_program.program.as_slice()
+        || &computed_pubkey_hash[1][..] == witness_program.program.as_slice()

197-223: P2WSH: enforce witness format and fix equality.

Validate v0/32-bytes and use slice equality in all branches.

-    let computed_script_hash = env::sha256_array(&witness_script);
-
-    if computed_script_hash == witness_program.program.as_slice() {
+    let computed_script_hash = env::sha256_array(&witness_script);
+    if witness_program.version != 0 || witness_program.program.len() != 32 {
+        return false;
+    }
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
@@
-    let computed_script_hash = env::sha256_array(&witness_script);
-    if computed_script_hash == witness_program.program.as_slice() {
+    let computed_script_hash = env::sha256_array(&witness_script);
+    if &computed_script_hash[..] == witness_program.program.as_slice() {
         return true;
     }
@@
-    let computed_script_hash = env::sha256_array(&witness_script);
-    computed_script_hash == witness_program.program.as_slice()
+    let computed_script_hash = env::sha256_array(&witness_script);
+    &computed_script_hash[..] == witness_program.program.as_slice()
bip322/src/signature.rs (3)

37-42: Doc incorrect: Compact path does not use BIP-340 tagged hash.

The Compact variant uses classic “Bitcoin Signed Message” double-SHA256, not BIP-340 tagged hashing.

-    /// This is the standard Bitcoin message signing format used by most wallets.
-    /// For BIP-322 simple signatures, the message is hashed directly with BIP-340
-    /// tagged hash, not through transaction construction.
+    /// This is the standard Bitcoin message signing format used by most wallets.
+    /// Compact signatures hash the message using the classic Bitcoin Signed Message
+    /// format (double SHA256 over CompactSize(prefix) + prefix +
+    /// CompactSize(message.len) + message), not through transaction construction.

452-479: Bitcoin Signed Message hash is incorrect (missing prefix length varint).

The standard requires CompactSize(len(prefix)) before the prefix. Without it, signatures from standard wallets won’t verify.

-    /// This follows the Bitcoin Core format:
-    /// Hash = SHA256(SHA256("Bitcoin Signed Message:\n" + varint(message.len()) + message))
+    /// This follows the Bitcoin Core format:
+    /// Hash = SHA256(SHA256(varint(len(prefix)) + prefix
+    ///     + varint(message.len()) + message))
     fn compute_bitcoin_message_hash(message: &str) -> [u8; 32] {
@@
-        // Create the full message with prefix and length
+        // Create the full message with prefix length, prefix and message length
         let mut full_message = Vec::new();
-        full_message.extend_from_slice(prefix);
+        // Write CompactSize of prefix length first
+        Self::encode_varint(prefix.len() as u64, &mut full_message);
+        full_message.extend_from_slice(prefix);
@@
-        // Double SHA256 hash
+        // Double SHA256 hash
         DoubleSha256::digest(&full_message).into()
     }

375-389: Do not fabricate zeroed keys; return None for compressed key case.

Returning [0u8; 64] on a valid compressed-key path is dangerous and can leak fake key material to callers. Validate and return None until proper decompression or a typed return is implemented.

-            ParsedPublicKey::Compressed(compressed) => {
-                // Validate compressed public key against address
-                if crate::verification::validate_compressed_pubkey_matches_address(
-                    compressed, address,
-                ) {
-                    // Validation succeeded, but we cannot provide uncompressed format
-                    // This indicates a successful verification but inability to decompress
-                    // For now, we'll create a placeholder uncompressed key to indicate success
-                    // TODO: Implement proper decompression or change API to accept compressed keys
-                    Some([0u8; 64]) // Placeholder indicating successful validation
-                } else {
-                    None
-                }
-            }
+            ParsedPublicKey::Compressed(compressed) => {
+                if crate::verification::validate_compressed_pubkey_matches_address(
+                    compressed, address,
+                ) {
+                    // Verified, but we cannot materialize an uncompressed key in this API
+                    // TODO: add a typed return (compressed/uncompressed) or decompress properly
+                    None
+                } else {
+                    None
+                }
+            }
🧹 Nitpick comments (16)
bip322/README.md (2)

55-61: Clarify verify() return semantics given compressed key limitation.

Usage prints a recovered public key, but the Limitations section states compressed keys currently return a placeholder [0u8; 64]. Add a note here (or adjust the example) to avoid implying a usable public key is always returned.


66-74: Make “Mainnet Only” constraints explicit (HRP/version).

Consider stating explicitly that only HRP "bc" and segwit v0 (P2WPKH, P2WSH) are supported. If testnet/regtest aren’t supported yet, adding a short note here will preempt confusion.

bip322/validate_unisat_vector.py (5)

27-29: Avoid bare except; catch Exception explicitly.

Using bare except obscures real errors and complicates debugging.

-    except:
-        pass
+    except Exception:
+        return None

8-8: Remove unused import.

unhexlify isn’t used.

-from binascii import hexlify, unhexlify
+from binascii import hexlify

154-154: Remove f-string without placeholders.

-        print(f"✓ Address parsed successfully")
+        print("✓ Address parsed successfully")

167-178: Check library availability without importing to avoid F401.

Use importlib.util.find_spec instead of importing modules you don’t use.

-    try:
-        import bech32
-        print("✓ bech32 library available")
-    except ImportError:
-        print("✗ bech32 library not available - run: pip install bech32")
-    
-    try:
-        import ecdsa
-        print("✓ ecdsa library available")
-    except ImportError:
-        print("✗ ecdsa library not available - run: pip install ecdsa")
+    import importlib.util
+    if importlib.util.find_spec("bech32") is not None:
+        print("✓ bech32 library available")
+    else:
+        print("✗ bech32 library not available - run: pip install bech32")
+    if importlib.util.find_spec("ecdsa") is not None:
+        print("✓ ecdsa library available")
+    else:
+        print("✗ ecdsa library not available - run: pip install ecdsa")

101-124: Address validation is a stub; consider computing HASH160.

Returning True unconditionally can be misleading. If feasible, compute RIPEMD160(SHA256(pubkey)) and compare to the P2WPKH witness program. If RIPEMD160 isn’t available, gate the check and emit a clear message.

bip322/validate_unisat_comprehensive.py (4)

26-27: Avoid bare except; catch Exception explicitly.

-    except:
-        pass
+    except Exception:
+        return None

8-8: Remove unused import.

unhexlify isn’t used.

-from binascii import hexlify, unhexlify
+from binascii import hexlify

81-170: Manual ECDSA key recovery is error-prone; prefer proven libraries.

The ad‑hoc point reconstruction and parity handling is fragile and easy to get subtly wrong. If possible, use a library that exposes compact signature recovery (e.g., coincurve or libsecp256k1 bindings). Keep the current code as a debug path only.


204-204: Remove f-string without placeholders.

-        print(f"✓ Address parsed successfully")
+        print("✓ Address parsed successfully")
core/src/payload/bip322.rs (1)

12-14: Implement extract_defuse_payload in SignedBip322Payload to match other payloads

The DefusePayload<T> struct is defined as:

pub struct DefusePayload<T> {
    pub signer_id: AccountId,
    pub verifying_contract: AccountId,
    pub deadline: Deadline,
    pub nonce: Nonce,
    #[serde(flatten)]
    pub message: T,
}

Most Signed*Payload types simply parse a JSON string into DefusePayload<T>:

impl<T> ExtractDefusePayload<T> for SignedWebAuthnPayload
where
    T: DeserializeOwned,
{
    type Error = serde_json::Error;

    #[inline]
    fn extract_defuse_payload(self) -> Result<DefusePayload<T>, Self::Error> {
        serde_json::from_str(&self.payload)
    }
}

For BIP-322, however, the envelope fields (signer_id, verifying_contract, deadline, nonce) live on SignedBip322Payload itself, and only message is JSON‐encoded. You should:

  • Parse self.message into T.
  • Construct and return a DefusePayload<T> using self.signer_id, self.verifying_contract, self.deadline, self.nonce, and the parsed message.
File: core/src/payload/bip322.rs

     impl<T> ExtractDefusePayload<T> for SignedBip322Payload
     where
         T: DeserializeOwned,
     {
         type Error = serde_json::Error;

-        fn extract_defuse_payload(self) -> Result<super::DefusePayload<T>, Self::Error> {
-            todo!()
-        }
+        fn extract_defuse_payload(self) -> Result<super::DefusePayload<T>, Self::Error> {
+            // Deserialize the inner message JSON into T
+            let msg: T = serde_json::from_str(&self.message)?;
+
+            // Wrap envelope fields and the deserialized message
+            Ok(super::DefusePayload {
+                signer_id: self.signer_id,
+                verifying_contract: self.verifying_contract,
+                deadline: self.deadline,
+                nonce: self.nonce,
+                message: msg,
+            })
+        }
     }
bip322/src/lib.rs (1)

43-47: Avoid recomputing message hash in verify().

hash() and verify() both compute the same hash. Consider passing hash() into verification to avoid duplication.

 impl SignedPayload for SignedBip322Payload {
     type PublicKey = <Secp256k1 as Curve>::PublicKey;

     fn verify(&self) -> Option<Self::PublicKey> {
-        let message_hash = self
-            .signature
-            .compute_message_hash(&self.message, &self.address);
-        self.signature
-            .extract_public_key(&message_hash, &self.address)
+        let message_hash = self.hash();
+        self.signature.extract_public_key(&message_hash, &self.address)
     }
 }

Also applies to: 53-59

bip322/src/tests.rs (1)

339-361: Rename or fix assertion message: failure is due to invalid signature, not length.

The test name and message mention “Invalid signature length” but the provided signature is 65 bytes; the verification fails because the signature content is invalid.

-    fn test_signature_verification_invalid_signature_length() {
+    fn test_signature_verification_invalid_signature() {
@@
-        assert!(
-            result.is_none(),
-            "Invalid signature length should fail verification"
-        );
+        assert!(result.is_none(), "Invalid signature should fail verification");
bip322/src/verification.rs (2)

143-154: P2PKH: use slice equality consistently.

Array-to-slice comparisons can be brittle; compare slices explicitly.

-    if uncompressed_hash[0] == *expected_pubkey_hash {
+    if &uncompressed_hash[0][..] == expected_pubkey_hash {
         return true;
     }
@@
-    compressed_hash[0] == *expected_pubkey_hash || compressed_hash[1] == *expected_pubkey_hash
+    &compressed_hash[0][..] == expected_pubkey_hash || &compressed_hash[1][..] == expected_pubkey_hash

169-195: P2SH: fix slice equality for script-hash comparisons.

Ensure comparisons are slice-to-slice for consistency.

-    if computed_script_hash == *expected_script_hash {
+    if &computed_script_hash[..] == expected_script_hash {
         return true;
     }
@@
-    if computed_script_hash == *expected_script_hash {
+    if &computed_script_hash[..] == expected_script_hash {
         return true;
     }
@@
-    computed_script_hash == *expected_script_hash
+    &computed_script_hash[..] == expected_script_hash
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4371d67 and b11242f.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • bip322/unisat-failure.png is excluded by !**/*.png
📒 Files selected for processing (22)
  • .gitignore (1 hunks)
  • Cargo.toml (3 hunks)
  • bip322/Cargo.toml (1 hunks)
  • bip322/README.md (1 hunks)
  • bip322/src/bitcoin_minimal.rs (1 hunks)
  • bip322/src/error.rs (1 hunks)
  • bip322/src/hashing.rs (1 hunks)
  • bip322/src/lib.rs (1 hunks)
  • bip322/src/signature.rs (1 hunks)
  • bip322/src/tests.rs (1 hunks)
  • bip322/src/transaction.rs (1 hunks)
  • bip322/src/verification.rs (1 hunks)
  • bip322/validate_unisat_comprehensive.py (1 hunks)
  • bip322/validate_unisat_vector.py (1 hunks)
  • core/Cargo.toml (2 hunks)
  • core/src/payload/bip322.rs (1 hunks)
  • core/src/payload/mod.rs (1 hunks)
  • core/src/payload/multi.rs (5 hunks)
  • near-utils/src/digest.rs (3 hunks)
  • tests/Cargo.toml (1 hunks)
  • tests/src/tests/defuse/mod.rs (2 hunks)
  • tests/src/utils/crypto.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • tests/Cargo.toml
  • bip322/src/transaction.rs
  • bip322/src/error.rs
  • core/src/payload/multi.rs
  • bip322/src/hashing.rs
  • tests/src/tests/defuse/mod.rs
  • tests/src/utils/crypto.rs
  • Cargo.toml
  • .gitignore
  • bip322/src/bitcoin_minimal.rs
  • core/Cargo.toml
  • bip322/Cargo.toml
🧰 Additional context used
🧬 Code Graph Analysis (7)
bip322/validate_unisat_comprehensive.py (1)
bip322/validate_unisat_vector.py (4)
  • parse_bech32_address (15-29)
  • bitcoin_message_hash (31-52)
  • recover_pubkey_from_signature (54-99)
  • main (129-184)
bip322/src/lib.rs (1)
core/src/payload/multi.rs (2)
  • hash (67-78)
  • verify (85-96)
core/src/payload/bip322.rs (2)
core/src/payload/mod.rs (2)
  • extract_defuse_payload (51-51)
  • extract_defuse_payload (58-60)
core/src/payload/multi.rs (1)
  • extract_defuse_payload (106-117)
bip322/src/signature.rs (4)
bip322/src/transaction.rs (2)
  • create_to_sign (101-137)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (4)
  • from (851-855)
  • new (144-146)
  • new (436-438)
  • new (467-469)
bip322/src/verification.rs (2)
  • validate_pubkey_matches_address (23-37)
  • validate_compressed_pubkey_matches_address (52-90)
bip322/src/hashing.rs (2)
  • compute_message_hash (61-74)
  • compute_bip322_message_hash (38-44)
bip322/validate_unisat_vector.py (1)
bip322/validate_unisat_comprehensive.py (4)
  • parse_bech32_address (15-28)
  • bitcoin_message_hash (30-51)
  • recover_pubkey_from_signature (53-179)
  • main (181-229)
near-utils/src/digest.rs (1)
bip322/src/bitcoin_minimal.rs (5)
  • default (138-140)
  • default (430-432)
  • new (144-146)
  • new (436-438)
  • new (467-469)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (3)
  • compute_tx_id (151-157)
  • create_to_sign (101-137)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (6)
  • new (144-146)
  • new (436-438)
  • new (467-469)
  • from_str (232-356)
  • all_zeros (450-452)
  • from_byte_array (454-456)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (38-44)
bip322/src/lib.rs (2)
  • hash (44-47)
  • verify (53-59)
🪛 Ruff (0.12.2)
bip322/validate_unisat_comprehensive.py

8-8: binascii.unhexlify imported but unused

Remove unused import: binascii.unhexlify

(F401)


26-26: Do not use bare except

(E722)


56-56: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


58-58: ecdsa.ecdsa.possible_public_keys_from_signature imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


84-84: bitcoinlib.keys.Key imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


135-135: Local variable point is assigned to but never used

Remove assignment to unused variable point

(F841)


204-204: f-string without any placeholders

Remove extraneous f prefix

(F541)

bip322/validate_unisat_vector.py

8-8: binascii.unhexlify imported but unused

Remove unused import: binascii.unhexlify

(F401)


27-27: Do not use bare except

(E722)


57-57: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


58-58: ecdsa.curves.SECP256k1 imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


59-59: ecdsa.ellipticcurve.Point imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


120-120: Local variable sha256_hash is assigned to but never used

Remove assignment to unused variable sha256_hash

(F841)


154-154: f-string without any placeholders

Remove extraneous f prefix

(F541)


165-165: Local variable recovered_pubkey is assigned to but never used

Remove assignment to unused variable recovered_pubkey

(F841)


169-169: bech32 imported but unused; consider using importlib.util.find_spec to test for availability

(F401)


175-175: ecdsa imported but unused; consider using importlib.util.find_spec to test for availability

(F401)

🪛 LanguageTool
bip322/README.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...EAR blockchain ecosystem. ## 🎯 Purpose This module provides **complete BIP-322 s...

(QB_NEW_EN)


[grammar] ~12-~12: There might be a mistake here.
Context: ... using only NEAR SDK host functions - 📋 Wide Coverage: Supports most major Bi...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...types (P2PKH, P2SH, P2WPKH, P2WSH) - ⚡ Gas Optimized: Minimal gas consumption...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...gh efficient NEAR SDK integration - 🔒 Security Focused: Comprehensive valida...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...ation with proper error handling - 🧪 Well Tested: Extensive test suite with ...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...fficial BIP-322 reference vectors ## 🏗️ Architecture ### Core Components - **`...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...2 reference vectors ## 🏗️ Architecture ### Core Components - lib.rs: Main `Si...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...nd SignedPayload trait implementations - signature.rs: BIP-322 signature parsing and verifica...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...signature parsing and verification logic - bitcoin_minimal.rs: Minimal Bitcoin types optimized for BI...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...P-322 (transactions, addresses, scripts) - hashing.rs: BIP-322 message hash computation with ...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...h computation with proper tagged hashing - transaction.rs: BIP-322 "to_spend" and "to_sign" trans...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - verification.rs: Address validation and public key reco...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...se64 signature decoding ## 🚀 Usage rust use defuse_bip322::SignedBip322Payload; use defuse_crypto::SignedPayload; // Parse and verify a BIP-322 signature let payload = SignedBip322Payload { address: "bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l".parse()?, message: "Hello Bitcoin!".to_string(), signature: "AkcwRAIgeGl4sSPd7zEIvhxdN8GgP4vgSqA8TdyPMeIpCF4gqgE4AiBsjQd0D1OFxdnHQPNOI1YdGlBD6kEOGRnHhcAkHnxUcAH=".parse()?, }; // Verify signature and extract public key if let Some(public_key) = payload.verify() { println!("✅ Valid BIP-322 signature!"); println!("🔑 Public key: {:?}", public_key); } else { println!("❌ Invalid signature"); } ``` ## 📊 Supported Features ### ✅ Address Type...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...ature"); } ``` ## 📊 Supported Features ### ✅ Address Types (Mainnet Only) | Type | ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...) | Type | Format | Example | Support | |------|--------|---------|---------| | ...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ... | |------|--------|---------|---------| | P2PKH | Legacy addresses starting ...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...Gefi2DMPTfTL5SLmv7DivfNa` | ✅ Complete | | P2SH | Script addresses starting w...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...iAE6uzMj2ZifT9YgRrkSgzQX` | ✅ Complete | | P2WPKH | Bech32 addresses starting...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...rlzms0wvx3gsqjx7vavgkx0l` | ✅ Complete | | P2WSH | Bech32 script addresses (3...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...: 65-byte compact format (P2PKH, P2WPKH) - Full Signatures: Complete BIP-322 witn...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...P-322 witness stack format (P2SH, P2WSH) - Automatic Detection: Parses both forma...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...-signed-message" tagged hash computation - Transaction Structure: Correct "to_spe...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ..." and "to_sign" transaction construction - Witness Handling: Complete witness sta...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...ete witness stack parsing and validation - Address Validation: Full address forma...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...n ## 🔍 Discovered Issues & Limitations During implementation and testing, severa...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...e not currently supported. Details: - P2TR uses Taproot (BIP-341) with differe...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...s compressed 33-byte keys. Details: - NEAR SDK ecrecover returns 64-byte unc...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...hem. Implementation of the decompression inside contract is computationally inten...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...es not provide a way to uncompress keys. - See TODO at `bip322/src/signature.rs:384...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...signature.rs:384` Current Behavior: - Compressed key validation works correctl...

(QB_NEW_EN)


[grammar] ~133-~133: There might be a mistake here.
Context: ...tion Results** (see validation scripts): 1. Python Verification: External Bitcoin ...

(QB_NEW_EN)


[grammar] ~138-~138: There might be a mistake here.
Context: ...to the given address Evidence: See unisat-failure.png - screenshot showing verif...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ... appears to be invalid, possibly due to: - Incorrect signature generation by the wa...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...rrect signature generation by the wallet - Wrong message format during signing - Co...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...et - Wrong message format during signing - Copy/paste errors in the test vector **...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...ted as expecting failure. ## 🧪 Testing The module includes comprehensive testing...

(QB_NEW_EN)


[grammar] ~153-~153: There might be a mistake here.
Context: ...g, message hashing, transaction building - Integration Tests: End-to-end signatur...

(QB_NEW_EN)


[grammar] ~154-~154: There might be a mistake here.
Context: ...-to-end signature verification workflows - Reference Vectors: Official BIP-322 te...

(QB_NEW_EN)


[grammar] ~155-~155: There might be a mistake here.
Context: ...-322 test vectors from the specification - Edge Cases: Invalid signatures, malfor...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: .../29 tests passing** (98.6% success rate) - 1 test ignored (invalid Unisat vector) -...

(QB_NEW_EN)


[grammar] ~161-~161: There might be a mistake here.
Context: ...- 1 test ignored (invalid Unisat vector) - All official BIP-322 reference vectors p...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...valid cases ## 📄 Standards Compliance - ✅ BIP-322: Complete implementation of G...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...ntation of Generic Signed Message Format - ✅ BIP-143: Segwit transaction digest a...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...3**: Segwit transaction digest algorithm - ✅ Base58Check: Legacy address encoding...

(QB_NEW_EN)


[grammar] ~170-~170: There might be a mistake here.
Context: ...*: Legacy address encoding (P2PKH, P2SH) - ✅ Bech32: Segwit address encoding (P2W...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...oding (P2WPKH, P2WSH) ## 🤝 Integration This module integrates seamlessly with th...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Security Audit - report
🔇 Additional comments (6)
near-utils/src/digest.rs (3)

37-73: Hash160 implementation via NEAR host functions looks solid.

Correct composition RIPEMD160(SHA256(data)) with host calls; trait impls and output size (U20) align with Bitcoin expectations.


112-129: TaggedDigest is correctly implemented (tag||tag||data).

Good reusable primitive for domain-separated hashing.


161-169: DoubleSha256 test matches expected host behavior. LGTM.

core/src/payload/mod.rs (1)

1-1: Expose bip322 module in payload namespace. LGTM.

This enables the new payload type without affecting existing modules.

bip322/src/lib.rs (1)

42-60: LGTM: cohesive API and clean integration into defuse-crypto traits.

The SignedBip322Payload struct and its Payload/SignedPayload impls are consistent and align with MultiPayload dispatch. Public re-exports are tidy and helpful.

bip322/src/signature.rs (1)

404-428: Sanity-check: recovery ID range and ecrecover usage look correct.

Header 27–34 mapped to v=0–3 with compressed/uncompressed handling is consistent with Bitcoin compact signatures, and env::ecrecover call shape matches.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
bip322/src/tests.rs (1)

370-381: Fix test intent mismatch: comment says “expects failure” but helper asserts success (Unisat).

This is the same issue previously flagged and still present. The test claims the vector is invalid and expects failure, but it calls a helper that unwraps on success.

Apply this diff to assert failure inline:

@@
-        test_parse_bip322_payload(address, signature, "unisat");
+        // Inline the logic to assert failure, instead of delegating to the success-helper
+        use crate::Bip322Signature;
+        let bip322_signature = Bip322Signature::from_str(signature).expect("parse base64");
+        let payload = SignedBip322Payload {
+            address: address.parse().unwrap(),
+            message: MESSAGE.to_string(),
+            signature: bip322_signature,
+        };
+        assert!(
+            payload.verify().is_none(),
+            "Expected verification failure for unisat"
+        );
🧹 Nitpick comments (8)
bip322/src/tests.rs (8)

382-395: Clarify helper semantics to avoid mis-use

This helper unwraps on success. A short doc comment will prevent using it in failure-expected tests.

@@
-    fn test_parse_bip322_payload(address: &str, signature: &str, info_message: &str) {
+    /// Helper that asserts the BIP-322 payload verifies successfully.
+    fn test_parse_bip322_payload(address: &str, signature: &str, info_message: &str) {

295-316: Rename to match what’s tested (not “wrong witness type”)

This test uses an empty 65-byte compact signature with a P2PKH address. It’s not actually asserting a “wrong witness type” scenario; it’s asserting an invalid/empty compact signature fails.

@@
-    fn test_signature_verification_wrong_witness_type() {
+    fn test_signature_verification_empty_compact_signature() {
@@
-        // Create a P2PKH address but use P2WPKH witness - should fail
+        // Create a P2PKH address and use an empty/invalid compact signature - should fail

If you want to explicitly test “wrong witness/address type,” consider parsing a known-good Full (P2WPKH) signature and verifying it against a P2PKH address (or vice versa), asserting failure.


340-361: Rename and adjust assertion message: it’s not a length test

The constructed signature is a valid-length (65-byte) compact signature filled with zeros. The failure is due to invalid contents, not length.

@@
-    fn test_signature_verification_invalid_signature_length() {
+    fn test_signature_verification_invalid_signature_contents() {
@@
-        assert!(
-            result.is_none(),
-            "Invalid signature length should fail verification"
-        );
+        assert!(
+            result.is_none(),
+            "Invalid/empty compact signature should fail verification"
+        );

248-261: Assert OP_RETURN script in to_sign output to fully validate structure

You already check value = 0. Adding a script check makes the test stricter and aligns with the implementation emitting OP_RETURN.

@@
-        // Verify output is OP_RETURN (unspendable)
-        let output = &to_sign.output[0];
-        assert_eq!(output.value, 0, "Output value should be zero");
+        // Verify output is OP_RETURN (unspendable)
+        let output = &to_sign.output[0];
+        assert_eq!(output.value, 0, "Output value should be zero");
+        // OP_RETURN opcode is 0x6a
+        assert_eq!(
+            output.script_pubkey.as_bytes(),
+            &[0x6a],
+            "Output script_pubkey should be OP_RETURN"
+        );

132-143: setup_test_env likely unnecessary in message hashing tests

These hashing tests don’t appear to require a NEAR VM context. Dropping setup_test_env() will simplify and speed the tests.

Also applies to: 144-158, 160-179, 181-194


110-125: Optionally assert specific error variants for invalid addresses

You have the expected variants listed but don’t assert them. If AddressError exposes variants publicly, consider using matches! to assert specific errors. This catches regressions in parsing behavior.

Example:

for (addr_str, expected) in invalid_addresses {
    let err = Address::from_str(addr_str).unwrap_err();
    match expected {
        AddressError::UnsupportedFormat => assert!(matches!(err, AddressError::UnsupportedFormat)),
        AddressError::InvalidLength => assert!(matches!(err, AddressError::InvalidLength)),
        AddressError::InvalidBech32 => assert!(matches!(err, AddressError::InvalidBech32 | AddressError::InvalidWitnessProgram)),
        _ => {}
    }
}

535-576: Consider making “working vectors” a strict test once implementation settles

Currently failures just log. To ensure interoperability doesn’t silently regress, consider asserting that all working vectors verify (or gate strictness behind a cargo feature).


928-928: Run cargo fmt on this file

The formatter check flagged multiple diffs. Running cargo fmt will normalize style and avoid CI noise.

Also applies to: 938-938, 949-949, 959-959, 970-970, 980-980, 990-990, 1000-1000, 1011-1011, 1021-1021

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b11242f and 777bae9.

📒 Files selected for processing (1)
  • bip322/src/tests.rs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (3)
  • compute_tx_id (151-157)
  • create_to_sign (101-137)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (6)
  • new (144-146)
  • new (436-438)
  • new (467-469)
  • from_str (232-356)
  • all_zeros (450-452)
  • from_byte_array (454-456)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (38-44)
bip322/src/lib.rs (2)
  • hash (44-47)
  • verify (53-59)
🪛 GitHub Check: Format
bip322/src/tests.rs

[warning] 1021-1021:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 1011-1011:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 1000-1000:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 990-990:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 980-980:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 970-970:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 959-959:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 949-949:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 938-938:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs


[warning] 928-928:
Diff in /home/runner/work/intents/intents/bip322/src/tests.rs

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Security Audit - report
🔇 Additional comments (2)
bip322/src/tests.rs (2)

1-10: Great breadth and structure of the BIP-322 test suite

Comprehensive coverage across address parsing, message hashing, tx building, signature parsing, official vectors, and wallet-generated vectors. This will pay off in interoperability.


439-446: Resolve inconsistent expectations for Unisat P2WPKH test vector

Two tests use the same address/signature (bc1qyt6gau643sm52hvej4n4qr34h3878ahs209s27 + H6Gjb…DU=) but contradict each other:

  • Earlier in bip322/src/tests.rs (around line 373), the test_parse_bip322_payload(address, signature, "unisat") vector is documented to expect failure.
  • Later in the Bip322TestVector list (lines 439–446), the same address/signature is marked with expected_verification: true.

Please confirm the correct BIP-322 outcome for Unisat’s signing method and hash domain:

  • If it should fail, change expected_verification to false (or remove the vector).
  • If it should succeed, update the earlier test’s comment/assertion to expect success.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
bip322/src/tests.rs (2)

382-395: Helper only asserts success; rename or make it accept an expected outcome

This helper always unwraps success and is incompatible with tests that expect failure. Either (a) rename to clarify intent (e.g., assert_valid_bip322_signature) or (b) add a should_verify: bool parameter.

Example refactor:

-fn test_parse_bip322_payload(address: &str, signature: &str, info_message: &str) {
+fn test_parse_bip322_payload(
+    address: &str,
+    signature: &str,
+    info_message: &str,
+    should_verify: bool,
+) {
     use crate::Bip322Signature;
 
     let bip322_signature = Bip322Signature::from_str(signature)
         .expect("Should parse signature from base64 string");
 
-    let _pubkey = SignedBip322Payload {
+    let payload = SignedBip322Payload {
         address: address.parse().unwrap(),
         message: MESSAGE.to_string(),
         signature: bip322_signature,
-    }
-    .verify()
-    .unwrap_or_else(|| panic!("Expected valid signature for {info_message}"));
+    };
+    let verified = payload.verify().is_some();
+    assert!(
+        verified == should_verify,
+        "Expected verification {} for {}",
+        if should_verify { "success" } else { "failure" },
+        info_message
+    );
 }

370-381: Fix test intent mismatch: this test expects failure but calls the success helper

The comment says this vector is invalid and should fail, but the code calls a helper that asserts success. Inline the failure assertion (or make the helper accept an expected result) so intent matches behavior.

Apply this diff:

@@
     fn test_parse_signed_bip322_payload_unisat_wallet() {
         // This test vector appears to be invalid - the signature does not verify against the address
         // Testing confirmed that neither Bitcoin message signing nor BIP-322 hashing produces
         // a public key that matches the given address. This test case expects failure.
         let address = "bc1qyt6gau643sm52hvej4n4qr34h3878ahs209s27";
         let signature = "H6Gjb7ArwmAtbS7urzjT1IS+GfGLhz5XgSvu2c863K0+RcxgOFDoD7Uo+Z44CK7NcCLY1tc9eeudsYlM2zCNYDU=";
 
-        test_parse_bip322_payload(address, signature, "unisat");
+        // Inline the logic to assert failure, instead of delegating to the success-helper
+        use crate::Bip322Signature;
+        let bip322_signature = Bip322Signature::from_str(signature).expect("parse base64");
+        let payload = SignedBip322Payload {
+            address: address.parse().unwrap(),
+            message: MESSAGE.to_string(),
+            signature: bip322_signature,
+        };
+        assert!(
+            payload.verify().is_none(),
+            "Expected verification failure for unisat"
+        );
     }
🧹 Nitpick comments (4)
bip322/src/tests.rs (4)

339-361: Misleading test name/message: it’s invalid content, not length

This uses a 65-byte (correct length) all-zero signature. Rename the test and tweak the assertion message for clarity.

Apply this diff:

-    fn test_signature_verification_invalid_signature_length() {
+    fn test_signature_verification_invalid_signature_content() {
@@
-        let invalid_signature = [0u8; 65]; // Valid 65-byte signature (but empty, so will fail)
+        let empty_signature = [0u8; 65]; // Correct length but invalid content (all zeros)
@@
-            signature: crate::Bip322Signature::Compact {
-                signature: invalid_signature,
-            },
+            signature: crate::Bip322Signature::Compact { signature: empty_signature },
@@
-        assert!(
-            result.is_none(),
-            "Invalid signature length should fail verification"
-        );
+        assert!(result.is_none(), "Invalid signature content should fail verification");

295-316: Clarify test intent: not “wrong witness type” but “unsupported compact signature for BIP-322”

The test constructs a P2PKH payload with a compact signature. For BIP-322, full witness-style signatures are expected. Rename for precision.

Apply this diff:

-    fn test_signature_verification_wrong_witness_type() {
+    fn test_signature_verification_unsupported_compact_signature() {
@@
-        // Create a P2PKH address but use P2WPKH witness - should fail
+        // Create a P2PKH address but use a compact signature (non-BIP-322) – should fail
@@
-        assert!(
-            result.is_none(),
-            "Wrong witness type should fail verification"
-        );
+        assert!(result.is_none(), "Unsupported signature format should fail verification");

110-125: Optionally assert exact error kinds for invalid addresses

You already enumerate expected error types but don’t assert them. Matching the concrete error variants would harden the tests.

Example:

let cases = vec![
    ("", AddressError::UnsupportedFormat),
    ("invalid", AddressError::UnsupportedFormat),
    ("1", AddressError::InvalidLength),
    ("bc1", AddressError::InvalidBech32),
];

for (s, expected) in cases {
    match Address::from_str(s) {
        Err(e) => assert!(matches!(e, _ if e == expected), "Expected {expected:?}, got {e:?} for '{s}'"),
        Ok(_) => panic!("Expected error for '{s}'"),
    }
}

258-261: Strengthen OP_RETURN assertion

You assert the output value is zero; consider also asserting the script starts with OP_RETURN (0x6a) to pin down intent. This improves regression resistance without affecting readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 777bae9 and 6cde859.

📒 Files selected for processing (1)
  • bip322/src/tests.rs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
bip322/src/tests.rs (5)
bip322/src/transaction.rs (3)
  • compute_tx_id (151-157)
  • create_to_sign (101-137)
  • create_to_spend (32-76)
bip322/src/bitcoin_minimal.rs (6)
  • new (144-146)
  • new (436-438)
  • new (467-469)
  • from_str (232-356)
  • all_zeros (450-452)
  • from_byte_array (454-456)
bip322/src/signature.rs (1)
  • from_str (84-98)
bip322/src/hashing.rs (1)
  • compute_bip322_message_hash (38-44)
bip322/src/lib.rs (2)
  • hash (44-47)
  • verify (53-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Check
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Shared security analysis / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared security analysis / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-contract, contract, near, defuse, 1, ., 1.18, false, --filter-paths "nod...
  • GitHub Check: Shared / Static Analysis (intents-poa-factory, contract, near, poa-factory, 1, ., 1.18, false, --filter-pa...
  • GitHub Check: Shared / Static Analysis (intents-poa-token, contract, near, poa-token, 1, ., 1.18, false, --filter-paths ...
  • GitHub Check: Security Audit - report
🔇 Additional comments (1)
bip322/src/tests.rs (1)

689-703: LGTM: Official message-hash vectors validated

Great to see exact matches against the BIP-322 reference hashes for empty and “Hello World” messages.

@fusede fusede marked this pull request as draft October 24, 2025 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants