Skip to content

deStore - Decentralized Marketplace for Digital Goods

Notifications You must be signed in to change notification settings

aaurelions/deStore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Decentralized Marketplace for Digital Goods

Abstract

This system is a decentralized marketplace allowing sellers to publish items (digital goods) via IPFS, while deliverers each hold only a partial encrypted piece of those goods. Buyers purchase items on-chain, and deliverers re-encrypt the relevant parts to the buyer upon detecting a valid purchase. If deliverers fail or act maliciously, the seller can step in after a fixed block threshold (100 blocks) to deliver the missing pieces and preserve the sale. All participants stake funds on-chain to ensure honest behavior, and only minimal data—such as store references, item IDs, sale records, and stake balances—is maintained on-chain.


1. Introduction & Motivation

Centralized solutions for digital goods distribution often rely on a single party—like a centralized server or escrow—to manage encryption keys, delivery, and refunds. This single point of failure can lead to security breaches and censorship.

In contrast, this system aims for:

  • Decentralized Delivery: Multiple untrusted deliverers collectively ensure the buyer receives the purchased digital item.
  • Minimal On-Chain Footprint: Storing large metadata or partial codes on-chain is expensive. Instead, we store only essential references (IPFS links, item IDs, payment receipts, stakes, and disputes).
  • Cryptographic Guarantees: By splitting or threshold-encrypting the digital good, no single deliverer has full access.
  • Economic Incentives: Staking and slashing ensure that dishonest deliverers or sellers risk losing their deposit if they cheat.

2. Roles & Responsibilities

  1. Seller

    • Creates a “store” on-chain with a reference to an IPFS JSON file containing the store’s items.
    • Each item in the JSON has a unique item ID (which we’ll also call purchaseId or itemId).
    • Splits the digital item (coupon code, license key, or other data) into multiple encrypted parts, each part intended for a distinct deliverer’s public key (but stored only in the IPFS JSON).
    • Stakes collateral on-chain to ensure honest behavior.
    • May update the IPFS reference (CID) if they need to remove or modify items or replace malicious deliverers.
  2. Buyer

    • Reads the store’s IPFS JSON (off-chain).
    • Checks the item’s price, itemId, descriptions, etc.
    • Initiates a purchase transaction on-chain, referencing the store and the itemId.
    • Waits for the deliverers (or fallback seller) to provide the encrypted fragments.
    • Decrypts the fragments off-chain using the buyer’s private key, reconstructing the digital good.
    • May open disputes if the item isn’t delivered or if the fragments are invalid.
  3. Deliverer

    • Stakes collateral on-chain, obtains an ephemeral public key or uses an off-chain method to broadcast their public key.
    • Scans all IPFS JSON entries from all sellers. Attempts to decrypt every stored fragment to discover which fragments belong to them.
    • Monitors on-chain for purchases. When a valid purchase is seen (matching the item’s price), they re-encrypt their fragment with the buyer’s public key, broadcasting the result on-chain via an event.
    • Earns a share of the commission from the seller for each successful delivery.
    • Risks losing stake if they deliver for an underpaid purchase or maliciously fail to deliver.
  4. DAO or Arbitrator (Optional)

    • Resolves edge-case disputes on-chain.
    • Slashes stakes of dishonest participants.
    • Conducts refunds to buyers if necessary.

3. System Overview

3.1 Off-Chain Storage & Encryption

  • IPFS JSON: For each store, the seller uploads a JSON object to IPFS. For example:
    {
      "storeName": "Alice's Digital Goods",
      "items": [
        {
          "itemId": "item-001",
          "title": "Special Discount Code",
          "price": 100000000000000000,   // 0.1 ETH
          "encryptedParts": [
            "0xABC123...",  // part for some deliverer
            "0xDEF456...",
            ...
          ],
          "otherMetadata": "... optional ..."
        },
        {
          "itemId": "item-002",
          "title": "Software License Key",
          "price": 200000000000000000,   // 0.2 ETH
          "encryptedParts": [ ... ]
        }
      ]
    }
    • encryptedParts: Each part is encrypted with a different deliverer’s public key. The JSON does not say which part belongs to which deliverer.
    • The price is in wei.
    • The buyer uses itemId when purchasing on-chain.

3.2 Smart Contract Minimalism

A single marketplace contract (or a factory pattern for multiple markets) that stores:

  • Stake Balances: delivererStakes[delivererAddress], sellerStakes[sellerAddress].

  • Store Registry: For each seller, we store:

    • sellerStoreCID[sellerAddress] = currentIPFSCID (the IPFS link to their store’s JSON).
  • Sold Items: A set or mapping indicating (sellerAddress, itemId) -> soldCount or boolean sold.

    • This ensures an item cannot be bought multiple times if the seller wants it to be unique (like a single-use coupon).
    • Alternatively, it can track how many times an item has been sold, if the seller wants multiple copies to be available.
  • Purchase Record: A minimal record of each purchase, e.g. (buyerAddress, sellerAddress, itemId, blockNumberPurchased, delivered, disputed, etc.).

    • Enough to handle refunds or disputes.
  • Fallback: Hardcoded at 100 blocks in the contract.

  • Events:

    • StoreCreated(seller, storeCID) or StoreUpdated(seller, oldCID, newCID)
    • ItemPurchased(buyer, seller, itemId, pricePaid)
    • FragmentDelivered(delivererOrSeller, buyer, seller, itemId, encryptedFragment)
    • DisputeOpened(...), DisputeResolved(...)

3.3 Purchase Flow

  1. Buyer Off-Chain Lookup:

    • Buyer fetches the store’s IPFS JSON via sellerStoreCID[sellerAddress].
    • Finds itemId and price.
  2. On-Chain Purchase:

    • Buyer calls marketplace.buyItem(seller, itemId) with msg.value = price.
    • The contract checks:
      • The item is not already sold out (if it’s unique).
      • The seller has an active store.
      • Buyer’s payment matches or exceeds the required price.
    • Emits ItemPurchased(buyer, seller, itemId, msg.value).
    • Marks (seller, itemId) as sold if it’s a unique item (or increments a sold counter if multiple copies are allowed).
  3. Delivery:

    • All deliverers watch for ItemPurchased(...).
    • Each deliverer compares msg.value to the expected price in IPFS to confirm correctness.
      • If the payment is correct, the deliverer tries to decrypt the relevant fragment from encryptedParts.
      • If successful, it re-encrypts that part with the buyer’s public key and calls deliverFragment(...), which emits a FragmentDelivered(...) event.
    • The buyer’s front-end listens for these events, retrieves each partial, and decrypts it off-chain.
  4. Fallback after 100 blocks:

    • If the buyer has not received all parts, the seller sees that certain fragments are missing.
    • After 100 blocks from purchase, the seller calls deliverFragment(...) for those missing parts.
    • The buyer then obtains them from the event logs.
  5. Reconstruction:

    • The buyer decrypts each part with their private key.
    • If it’s a simple split, the buyer concatenates the parts. If it’s a threshold scheme (e.g., Shamir’s Secret Sharing), the buyer reconstructs the secret from the partial shares.

3.4 Disputes & Refunds

  • If the buyer never receives valid fragments, they can call openDispute(...) referencing (seller, itemId).
  • If a deliverer delivered at the wrong price (underpaid), the seller can open a dispute to slash them.
  • By default, participants settle off-chain. If they fail, the DAO or an arbitrator calls resolveDispute(...), slashing any dishonest party’s stake and/or refunding the buyer if appropriate.

4. Detailed Smart Contract Outline

Below is a high-level design. The exact implementation can vary, but this captures the minimal data stored on-chain.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

contract DecentralizedMarketplace {
    // -------------------------------------------------
    // 1. Staking
    // -------------------------------------------------
    mapping(address => uint256) public delivererStakes;
    mapping(address => uint256) public sellerStakes;

    // Seller -> Current IPFS store reference
    mapping(address => string) public sellerStoreCID;

    // -------------------------------------------------
    // 2. Sale Tracking
    // -------------------------------------------------
    struct SaleInfo {
        address buyer;
        uint256 blockPurchased;
        bool delivered;    // if all parts delivered or final
        bool disputed;
        // Extend as needed
    }

    // (seller, itemId) -> SaleInfo
    // We assume each itemId can only be sold once if it's unique. 
    // Alternatively, store an array of SaleInfo for multiple copies.
    mapping(address => mapping(string => SaleInfo)) public sales;

    // -------------------------------------------------
    // 3. Events
    // -------------------------------------------------
    event DelivererStaked(address indexed deliverer, uint256 amount);
    event SellerStaked(address indexed seller, uint256 amount);

    event StoreCreated(address indexed seller, string ipfsCid);
    event StoreUpdated(address indexed seller, string oldCid, string newCid);

    event ItemPurchased(
        address indexed buyer,
        address indexed seller,
        string indexed itemId,
        uint256 price
    );
    
    event FragmentDelivered(
        address indexed sender,   // deliverer or fallback seller
        address indexed buyer,
        address indexed seller,
        string itemId,
        bytes encryptedFragment
    );

    event DisputeOpened(
        address indexed opener,
        address indexed seller,
        string indexed itemId
    );

    // For brevity, skipping DisputeResolved event, etc.

    // -------------------------------------------------
    // 4. Staking Logic
    // -------------------------------------------------
    function stakeAsDeliverer() external payable {
        require(msg.value > 0, "No stake provided");
        delivererStakes[msg.sender] += msg.value;
        emit DelivererStaked(msg.sender, msg.value);
    }

    function stakeAsSeller() external payable {
        require(msg.value > 0, "No stake provided");
        sellerStakes[msg.sender] += msg.value;
        emit SellerStaked(msg.sender, msg.value);
    }

    // -------------------------------------------------
    // 5. Store Creation/Update
    // -------------------------------------------------
    function createStore(string calldata ipfsCid) external {
        require(sellerStakes[msg.sender] > 0, "Seller not staked");
        require(bytes(sellerStoreCID[msg.sender]).length == 0, "Store already exists");

        sellerStoreCID[msg.sender] = ipfsCid;
        emit StoreCreated(msg.sender, ipfsCid);
    }

    function updateStore(string calldata newCid) external {
        require(sellerStakes[msg.sender] > 0, "Seller not staked");
        require(bytes(sellerStoreCID[msg.sender]).length != 0, "Store doesn't exist");

        string memory oldCid = sellerStoreCID[msg.sender];
        sellerStoreCID[msg.sender] = newCid;
        emit StoreUpdated(msg.sender, oldCid, newCid);
    }

    // -------------------------------------------------
    // 6. Purchase Logic
    // -------------------------------------------------
    function buyItem(address seller, string calldata itemId) external payable {
        require(sellerStakes[seller] > 0, "Seller not staked");
        // Check if it's not already sold (if unique):
        require(sales[seller][itemId].buyer == address(0), "Item already sold");

        // Minimal check: must pay > 0
        // Detailed price check is done off-chain by deliverers
        require(msg.value > 0, "Must send some ETH");

        // Record sale on-chain
        sales[seller][itemId] = SaleInfo({
            buyer: msg.sender,
            blockPurchased: block.number,
            delivered: false,
            disputed: false
        });

        emit ItemPurchased(msg.sender, seller, itemId, msg.value);
    }

    // -------------------------------------------------
    // 7. Delivery of Fragments
    // -------------------------------------------------
    function deliverFragment(
        address buyer,
        address seller,
        string calldata itemId,
        bytes calldata encryptedFragment
    ) external {
        // Could be any deliverer OR the fallback seller
        SaleInfo storage sale = sales[seller][itemId];
        require(sale.buyer == buyer, "Buyer does not match");
        require(!sale.disputed, "Sale is disputed");
        require(!sale.delivered, "Already delivered/final");

        // Fallback: if msg.sender == seller, check if 100 blocks have passed
        if (msg.sender == seller) {
            require(block.number > sale.blockPurchased + 100, "Fallback not available yet");
        } else {
            // If it's a deliverer, verify they are staked
            require(delivererStakes[msg.sender] > 0, "Deliverer not staked");
            // The deliverer should have verified off-chain that the price was correct
        }

        // Emit the partial data for the buyer to retrieve off-chain
        emit FragmentDelivered(msg.sender, buyer, seller, itemId, encryptedFragment);
    }

    // -------------------------------------------------
    // 8. Disputes
    // -------------------------------------------------
    function openDispute(address seller, string calldata itemId) external {
        SaleInfo storage sale = sales[seller][itemId];
        require(msg.sender == sale.buyer || msg.sender == seller, "Only buyer or seller can dispute");
        require(!sale.disputed, "Already disputed");
        sale.disputed = true;

        emit DisputeOpened(msg.sender, seller, itemId);
    }

    // A real system might have a DAO or arbitrator:
    // function resolveDispute(...) external onlyDAO { ... slash stakes ... }
}

Gas Minimization

  • Only critical data is stored:
    • Seller’s IPFS link,
    • Whether an item is sold,
    • Reference to the buyer and a few booleans for dispute/delivery status.
  • Everything else (price checks, partial encryption) is off-chain.

Updating the Store

  • The seller can update the IPFS CID anytime (e.g., removing malicious deliverers, changing items).
  • Once an item is sold, the contract blocks reselling the same itemId if it’s unique. If the seller wants multiple “copies,” they can incorporate an internal numeric suffix or repeated itemId logic in the JSON and keep track of sales differently.

5. Off-Chain Logic & Flow

  1. Seller

    • Splits or threshold-encrypts each digital good into N parts.
    • Embeds these encrypted parts in the JSON. No mention of which part belongs to which deliverer.
    • Uploads the JSON to IPFS.
    • Calls createStore(ipfsCid) (or updateStore(newCid)).
  2. Deliverers

    • Continuously parse all IPFS JSON from all active sellers. Attempt to decrypt each encryptedPart.
    • If it decrypts successfully, that fragment belongs to them.
    • Monitor ItemPurchased events. If payment matches the item’s price from the JSON, re-encrypt the part to the buyer’s key and call deliverFragment(...).
  3. Buyer

    • Reads store JSON from sellerStoreCID[seller].
    • Finds an item with itemId, checks the price, calls buyItem(seller, itemId) with the correct msg.value.
    • Watches for FragmentDelivered(sender, buyer, seller, itemId, part). Collects all parts, decrypts them off-chain, reconstructs the final digital item.
  4. Fallback

    • If deliverers have not delivered parts after 100 blocks, the seller calls deliverFragment(...) to ensure the buyer isn’t left without the purchased item.
  5. Dispute

    • If no valid fragments arrived or a deliverer delivered at the wrong price, participants open a dispute.
    • Off-chain negotiation might resolve it. If not, a DAO or governance process calls resolveDispute() to slash stakes or refund the buyer.

6. Preventing Re-Purchases & Tracking Sold Items

  • The contract keeps sales[seller][itemId].buyer to mark an item as sold.
  • Before the buyer front-end displays items from the store’s JSON, it queries the contract to see if (seller, itemId) is already sold. If yes, the UI hides or marks it as “Sold Out.”
  • This ensures the buyer cannot buy the same itemId multiple times.

7. Security & Incentives

7.1. Staking & Slashing

  • Deliverers stake ETH in delivererStakes. If they provide partial items for an underpaid purchase or never deliver at all (leading to a dispute they lose), they risk being slashed.
  • Sellers stake as well. If they create fake listings, refuse fallback, or cheat in a dispute, they risk losing stake.

7.2. Collusion Risk

  • If all deliverers collude, they could reconstruct the entire digital good. Economic incentives should discourage this, and the larger the pool of deliverers, the less likely all would collude.

7.3. Privacy Considerations

  • Deliverers see only the partial data they can decrypt. The buyer is the only one who can assemble all parts.
  • The seller knows the entire digital good by definition.

8. Example User Experience

  1. Seller Onboarding

    • Seller stakes ETH via stakeAsSeller().
    • Seller uploads store JSON to IPFS, calls createStore(cid) with the resulting IPFS hash.
  2. Publishing Items

    • The seller has items[] in the JSON, each with a unique itemId, price, and array of encryptedParts.
    • If updates are needed, the seller modifies the JSON, re-uploads, and calls updateStore(...).
  3. Buyer Browsing

    • Buyer’s front-end loads the store data from sellerStoreCID[seller].
    • Checks contract for each itemId to see if it’s sold. If not, shows “Buy” button.
  4. Buying

    • Buyer clicks “Buy.” Their wallet calls buyItem(seller, itemId) with the correct ETH.
    • The contract marks (seller, itemId) as sold, emits an event.
  5. Delivery

    • Each deliverer sees the ItemPurchased event, verifies off-chain that the buyer paid at least the price in the JSON.
    • If correct, each deliverer calls deliverFragment(buyer, seller, itemId, partEncryptedForBuyer).
    • Buyer’s front-end listens for FragmentDelivered events, collects N parts, decrypts, and reconstructs.
  6. Fallback

    • If some parts do not arrive after 100 blocks, the seller calls deliverFragment(...).
  7. Optional Dispute

    • If the buyer doesn’t receive correct parts, they call openDispute(seller, itemId).
    • A DAO or arbiter eventually resolves it. Possibly slashing stakes or refunding the buyer.

9. Future Extensions

  1. Threshold Cryptography: Instead of a simple split, use advanced threshold schemes (e.g., Shamir’s Secret Sharing) so only k-out-of-n deliverers are needed to reconstruct.
  2. Token Payments: Support ERC-20 tokens instead of ETH.
  3. Ratings & Reputation: Keep minimal integer rating or reputation indexes in the contract, or store them in IPFS for rich text feedback.
  4. Secondary Market: If items are transferrable or can be resold, one could represent them as NFTs referencing the data.
  5. Multi-Copy Sales: If multiple copies of the same item can be sold, store a soldCount and compare it to maxSupply.

Conclusion

This architecture:

  • Creates a store for each seller (on-chain pointer to an IPFS JSON).
  • Minimizes on-chain storage: We keep only the essential references for items sold, staking balances, and a fallback / dispute mechanism.
  • Enforces one-time or limited sales by marking items sold on-chain.
  • Ensures decentralized delivery via multiple deliverers scanning for fragments they can decrypt, re-encrypting them to the buyer’s key.
  • Provides fallback after 100 blocks, letting the seller rescue the sale.
  • Enables dispute resolution and slashing of dishonest parties’ stakes.

By carefully splitting logic between on-chain minimal records and off-chain IPFS + partial encryption, this system achieves both trust-minimized digital goods delivery and low gas usage. Buyers, sellers, and deliverers are economically incentivized to behave honestly through staking deposits and fear of slashing.

This completes the core design of a Decentralized Marketplace for Digital Goods with minimal on-chain data, robust off-chain encryption, seller-owned stores, and secure partial delivery.

About

deStore - Decentralized Marketplace for Digital Goods

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published