cryppadotta / dotta-license

ERC721-based Software Licensing Framework
502 stars 99 forks source link

Dotlicense

Decentralized software licensing

Dotlicense is an Ethereum ERC721-based software licensing framework.


Ethereum Token ERC721 Contributions Welcome MIT License Travis CI Join Us On Telegram
Built by Dotta and contributors

Overview

Dotlicense is a set of smart contracts and JavaScript tooling to sell and verify software licenses (e.g. in-app-purchases or feature access tokens) using Ethereum. It supports both single purchase and (prepaid) subscriptions.

The licenses are ERC721-compatible Tokens. The client app holds the private key that owns the token.

The benefits are:

  1. Piracy deterring -- Because the private key is used to validate the license, owners are dis-incentivized to share that key. Because if the key is shared, for example, on a file-sharing site the license can be transferred, stolen, and sold.
  2. Surveillance free -- There is no "license server" tracking the user. Ownership of the token is validated by any Web3 provider (e.g. Infura or even a self-hosted node)
  3. Scarce -- The number of licenses available for a given product can be limited
  4. Transferable -- Users can transfer or resell their licenses (e.g. they can be auctioned on sites such as Rarebits or OpenSea)
  5. Cryptocurrency-based -- Normal-cryptocurrency benefits apply such as near-instant payments, permissionless, decentralized ownership, etc. No approvals, Stripe, Shopify store, or bank account necessary.

It is designed for software licenses in desktop or mobile apps. (And there is discussion about using it for subscription web apps.)

Features

Implementations

Dotlicense is being used in Dottabot, a cryptocurrency trailing-profit stop bot built with Electron.

Packages

Dotlicense is split into several packages:

Additionally, this repo stores some utilities we've built along the way such as:

Purchase Model

There are two main models in the contracts:

And during operation we have:

The Products

Products have:

And optionally, if the product is a subscription, it may have:

The client unlocks features of a given Product id if ownership of a License is proven.

When a new product is created, the totalSupply is fixed and cannot be changed. A totalSupply of 0 means "unlimited".

When a new product is created, the renewal interval is fixed and cannot be changed. An interval of 0 means "unlimited".

The executives can:

The License

The License represents ownership of one unit of a Product. The License is the same as a "token" - they have the same ID and the two are used interchangeably.

Licenses have:

And optionally:

A License is created (that is, the token is minted) at time of sale. When a sale is made, the inventory for that Product is decremented and ownership is transferred to the assignee

An expirationTime of 0 means "does not expire".

The executives can:

Ownership

The private key that owns the license must be readable by the client software.

In Dottabot this private key is:

In Dottabot, this means that while it is encrypted on disk, it is also readable by the software without user interaction. (It can also be deployed onto a VPS where a hardware wallet may not be available.)

Of course, this raises the problem of funds at purchase: often our users will have an existing wallet that they spend from (and it won't be our application's private key).

To deal with this issue, we require the user input the assignee address at purchase time (that is, the address controlled by the client software). When the token is purchased, ownership of the token is given to the private key controlled by our software.

This helps fulfill the piracy deterrence requirement by incentivizing the user to keep the license private.

Contracts Overview

Contract Inheritance Architecture

The smart contracts are split into modules.

Roles-based Permissions

Issuance of new products and unsold inventory levels is centrally controlled. There are three roles:

Some of the smart contract functions are open to anyone and some are restricted by role. The table below shows the permissions for each:

function CEO CFO COO anyone
LicenseAccessControl
setCEO
setCFO
setCOO
setWithdrawalAddress
withdrawBalance
pause
unpause
LicenseBase
licenseProductId
licenseAttributes
licenseIssuedTime
licenseExpirationTime
licenseAffiliate
licenseInfo
LicenseInventory
createProduct
incrementInventory
decrementInventory
clearInventory
setPrice
setRenewable
priceOf
availableInventoryOf
totalSupplyOf
totalSold
intervalOf
renewableOf
productInfo
getAllProductIds
costForProductCycles
isSubscriptionProduct
LicenseOwnership (ERC721)
name
symbol
implementsERC721
tokenMetadata
supportsInterface
setTokenMetadataBaseURI
totalSupply
balanceOf
tokensOf
ownerOf
getApproved
isApprovedForAll
transfer
approve
approveAll
disapproveAll
takeOwnership
transferFrom
LicenseSale
setAffiliateProgramAddress
setRenewalsCreditAffiliatesFor
createPromotionalPurchase
createPromotionalRenewal
purchase
renew
LicenseCore
setNewAddress
unpause

Dotlicense Checkout

dot-license-checkout is a UMD (or React-component) library which embeds a Metamask-enabled checkout.

dot-checkout
// React Component 

const logo = require("path/to/logo.png");

const offers = [
  {
    productId: '1',
    duration: ONE_YEAR,
    name: '1 year',
    price: 0.15
  },
  {
    productId: '1',
    duration: ONE_YEAR * 2,
    name: '2 years',
    price: 0.15 * 2
  }
];

const config = {
  httpProviderURL: 'https://rinkeby.infura.io/98sadfnjncadlh8',
  licenseCoreAddress: '0xc3e2f9aADc4B5c467E0668C2d690a999A91A1a5C'
};

<DotLicenseCheckout
  productName="Dottabot"
  productSubheading="Unlimited License"
  offerLabel="Years:"
  offers={offers}
  logo={logo}
  httpProviderURL={config.httpProviderURL}
  licenseCoreAddress={config.licenseCoreAddress}
/>

CLI Tools

dot-license-cli

The CLI Admin tool has the following commands:

$ node ./bin/dot-license-cli.js --help
Usage: dot-license-cli.js <command> [options]

Commands:
  dot-license-cli.js affiliateProgram                                             Get the affiliate program address
  dot-license-cli.js approve <to> <tokenId>                                       Approves another address to claim for the ownership of the given token ID
  dot-license-cli.js approveAll <to>                                              Approves another address to claim for the ownership of any tokens owned by
                                                                                  this account
  dot-license-cli.js getApproved <tokenId>                                        Gets the approved address to take ownership of a given token ID
  dot-license-cli.js availableInventoryOf <productId>                             The available inventory of a product
  dot-license-cli.js balanceOf <owner>                                            Gets the balance of the specified address
  dot-license-cli.js ceoAddress                                                   Get the CEO's Address
  dot-license-cli.js cfoAddress                                                   Get the CFO's Address
  dot-license-cli.js clearInventory <productId>                                   clearInventory clears the inventory of a product.
  dot-license-cli.js cooAddress                                                   Get the COOs address
  dot-license-cli.js createProduct <productId> <initialPrice>                     createProduct creates a new product in the system
  <initialInventoryQuantity> <supply>
  dot-license-cli.js decrementInventory <productId> <inventoryAdjustment>         decrementInventory removes inventory levels for a product
  dot-license-cli.js disapproveAll <to>                                           Removes approval for another address to claim for the ownership of any
                                                                                  tokens owned by this account.
  dot-license-cli.js getAllProductIds                                             Get all product ids
  dot-license-cli.js incrementInventory <productId> <inventoryAdjustment>         incrementInventory - increments the inventory of a product
  dot-license-cli.js isApprovedForAll <owner> <operator>                     Tells whether an operator is approved by a given owner
  dot-license-cli.js licenseAttributes <licenseId>                                Get a license's attributes
  dot-license-cli.js licenseInfo <licenseId>                                      Get a license's info
  dot-license-cli.js licenseIssuedTime <licenseId>                                Get a license's issueTime
  dot-license-cli.js licenseProductId <licenseId>                                 Get a license's productId
  dot-license-cli.js name                                                         token's name
  dot-license-cli.js newContractAddress                                           Gets the new contract address
  dot-license-cli.js ownerOf <tokenId>                                            Gets the owner of the specified token ID
  dot-license-cli.js pause                                                        called by any C-level to pause, triggers stopped state
  dot-license-cli.js paused                                                       Checks if the contract is paused
  dot-license-cli.js priceOf <productId>                                          The price of a product
  dot-license-cli.js productInfo <productId>                                      The product info for a product
  dot-license-cli.js purchase <productId> <assignee> <affiliate>                  Purchase - makes a purchase of a product. Requires that the value sent is
                                                                                  exactly the price of the product
  dot-license-cli.js setAffiliateProgramAddress <address>                         executives *
  dot-license-cli.js setCEO <newCEO>                                              Sets a new CEO
  dot-license-cli.js setCFO <newCFO>                                              Sets a new CFO
  dot-license-cli.js setCOO <newCOO>                                              Sets a new COO
  dot-license-cli.js setNewAddress <v2Address>                                    Sets a new contract address
  dot-license-cli.js setPrice <productId> <price>                                 setPrice - sets the price of a product
  dot-license-cli.js setWithdrawalAddress <newWithdrawalAddress>                  Sets a new withdrawalAddress
  dot-license-cli.js symbol                                                       symbols's name
  dot-license-cli.js takeOwnership <tokenId>                                      Claims the ownership of a given token ID
  dot-license-cli.js tokensOf <owner>                                             Gets the list of tokens owned by a given address
  dot-license-cli.js totalSold <productId>                                        The total sold of a product
  dot-license-cli.js totalSupply                                                  Gets the total amount of tokens stored by the contract
  dot-license-cli.js totalSupplyOf <productId>                                    The total supply of a product
  dot-license-cli.js transfer <to> <tokenId>                                      Transfers the ownership of a given token ID to another address
  dot-license-cli.js transferFrom <from> <to> <tokenId>                           Transfer a token owned by another address, for which the calling address
                                                                                  has previously been granted transfer approval by the owner.
  dot-license-cli.js unpause                                                      Unpause the contract
  dot-license-cli.js withdrawBalance                                              Withdraw the balance to the withdrawalAddress
  dot-license-cli.js withdrawalAddress                                            Get the withdrawal address
  dot-license-cli.js info                                                         Describe contract info

Options:
  --web3              web3 provider url                                                                                   [default: "http://localhost:8545"]
  --from              from address
  --gasPrice          gas price in wei to use for this transaction                                                                   [default: "1000000000"]
  --gasLimit          maximum gas provided for this transaction                                                                         [default: "6500000"]
  --value             The value transferred for the transaction in wei
  --contract-address  address to contract                                                 [required] [default: "0xb4f53a030f9d088198cdb66b8ad95aa79a95868f"]
  --network-id        The network ID                                                                                                        [default: "101"]
  --ledger            use a ledger                                                                                                                 [boolean]
  --hd-path           hd-path (used for hardware wallets)                                                                          [default: "44'/60'/0'/0"]
  --help              Show help                                                                                                                    [boolean]
  --version           Show version number                                                                                                          [boolean]

Objections and Risks

Because licenses are verified on the client, this framework may be susceptible to at least two attacks: cracking and spoofing.

Cracking

Like any desktop, mobile, or client-run app it may be possible for a determined hacker to patch the binary in such a way as to bypass the verification mechanism. Over time, we expect to improve our deterrence methods, but cracking is always a risk.

Spoofing

Because this software uses the Ethereum blockchain to verify ownership of a license-token, one could "spoof" ownership by directing their Web3 provider to a chain fork where they own a token, even when they may have transferred that token on the main net.

Again, we plan to implement a degree of 'main-chain' verification to make this difficult or cumbersome for an attacker to do. But forks are always a risk.

This attack could be mitigated by hosting your own Ethereum node and requiring pinning in your client app. However, the tradeoff here is by requiring the user to hit your server the user has reduced privacy and availability.

FAQ

Configuring and deploying

(Coming soon)

Join Us On Telegram

If you're interested in using or developing Dotlicense, come join us on Telegram

Built With

With inspiration from:

Authors

Originally created by Dotta for Dottabot

License

MIT