Overview
The Intent contract (identipay::intent) provides a simple but critical function: verifying that the buyer signed the canonicalized intent hash using Ed25519.
This signature provides semantic binding of the buyer’s intent to the transaction execution, as described in the whitepaper section 5.3. Unlike traditional credit card payments where merchants can initiate charges, identiPay requires explicit buyer authorization via cryptographic signature.
Source Code
Location:contracts/sources/intent.move:5
How It Works
Proposal Creation
Merchant creates a commerce proposal (amount, items, constraints) and sends to buyer’s wallet.
Canonicalization
Buyer’s wallet canonicalizes the proposal JSON-LD using URDNA2015 to ensure consistent byte representation.
Signature
Wallet signs the intent hash using the user’s Ed25519 private key:
signature = Sign(k_buyer, intent_hash).Data Structures
This module has no custom data structures — it only performs signature verification.Public Functions
verify_intent_signature
Verify that a signature is valid for a given intent hash and public key. Aborts if invalid. Function Signature:Ed25519 signature (exactly 64 bytes)
SHA3-256 hash of the canonicalized proposal (32 bytes)
Buyer’s Ed25519 public key (exactly 32 bytes)
EInvalidSignatureLength(1): Signature is not 64 bytesEInvalidPublicKeyLength(2): Public key is not 32 bytesEEmptyIntentHash(3): Intent hash is emptyEInvalidSignature(0): Signature verification failed
public(package) — only callable by other modules in the identipay package (primarily settlement).
Location: intent.move:26-37
Usage Example
Intent Hash Computation
The intent hash is the SHA3-256 hash of the URDNA2015-canonicalized JSON-LD proposal.Why URDNA2015?
JSON has many equivalent representations:- Consistent key ordering
- Consistent whitespace
- Consistent Unicode normalization
- Canonical representation of JSON-LD semantics
Why SHA3-256?
SHA3-256 (Keccak) is used instead of SHA2-256 because:- Different construction: Sponge construction vs. Merkle-Damgård
- Length extension resistance: SHA3 is inherently resistant
- Modern standard: NIST standardized in 2015
- Sui native: Sui Move has built-in SHA3 support
Security Properties
Non-Repudiation
Non-Repudiation
Once a buyer signs an intent, they cannot deny having authorized the payment. The signature is cryptographic proof of authorization.
Semantic Binding
Semantic Binding
The signature binds to the semantic content of the proposal (amount, items, merchant), not just a generic “approve” message. This prevents bait-and-switch attacks.
Replay Protection
Replay Protection
The settlement contract tracks executed intent hashes to prevent replay. Even with a valid signature, the same intent cannot be executed twice.
Expiry Enforcement
Expiry Enforcement
Proposals include expiry timestamps. The settlement contract rejects expired proposals even if the signature is valid.
Attack Prevention
Bait-and-Switch Attack
Attack: Merchant shows user a proposal for 1000. Prevention: User’s wallet computes the hash of the displayed proposal and signs it. The on-chain signature verification ensures only the exact proposal that was signed can execute.Replay Attack
Attack: Merchant captures a valid signature and submits the same intent multiple times. Prevention: Settlement contract maintains a table of executed intent hashes (SettlementState.executed_intents). Second execution aborts with EIntentAlreadyExecuted.
Phishing Attack
Attack: Attacker tricks user into signing a malicious proposal. Prevention: Wallet UI must display proposal details clearly before requesting signature. Users should verify merchant identity via Trust Registry.Man-in-the-Middle Attack
Attack: Attacker intercepts proposal and modifies it before user signs. Prevention: Proposals should be transmitted over secure channels (HTTPS, WebSocket over TLS). Merchant DID provides authentication.Comparison to Traditional Payments
| Feature | Credit Cards | identiPay Intents |
|---|---|---|
| Authorization | Merchant-initiated (pull) | Buyer-initiated (push) |
| Binding | Generic card number | Cryptographic signature on specific proposal |
| Replay Protection | Limited (CVV, expiry) | Cryptographic (hash tracking) |
| Non-Repudiation | Weak (can dispute) | Strong (unforgeable signature) |
| Privacy | Reusable credentials | One-time stealth addresses |
| Fraud Risk | High (credential theft) | Low (no reusable credentials) |
Edge Cases
Empty Intent Hash
Empty Intent Hash
Aborts with
EEmptyIntentHash. This prevents accidental signing of null data.Wrong Key
Wrong Key
If the signature was created with a different private key than the provided public key, verification fails with
EInvalidSignature.Modified Hash
Modified Hash
If the intent hash is modified after signing, verification fails with
EInvalidSignature.Modified Signature
Modified Signature
Even a single bit flip in the signature causes verification to fail with
EInvalidSignature.Implementation Notes
Ed25519 Choice
identiPay uses Ed25519 (not ECDSA secp256k1) because:- Deterministic: No nonce generation, eliminates nonce-reuse attacks
- Fast: ~2x faster than ECDSA for signing
- Small signatures: 64 bytes (vs. 65-73 for ECDSA)
- Sui native: Sui Move has built-in
sui::ed25519::ed25519_verify()
Sui Native Verification
The contract uses Sui’s nativeed25519_verify() function:
Best Practices
Display Before Sign
Display Before Sign
Wallet UIs must display the full proposal details (amount, merchant name, items) before requesting the user’s signature. Never auto-sign.
Verify Merchant
Verify Merchant
Before showing the approval dialog, verify the merchant is registered in the Trust Registry and active.
Check Expiry
Check Expiry
Validate that
proposal.expiry > current_time before signing. Don’t sign expired proposals.Secure Key Storage
Secure Key Storage
Store Ed25519 private keys in secure enclaves (iOS Secure Enclave, Android Keystore, hardware wallets).
Related Modules
Settlement
Calls verify_intent_signature during execution
Trust Registry
Verify merchant legitimacy before signing
