Responsible eBook Lending with Soroban Smart Contracts

Smart eBook Lending

Library eBook Lending with Stellar Blockchain and Soroban Smart Contracts

Introduction

The modern day library faces the challenge of managing digital assets and intellectual property rights efficiently and legally while ensuring that eBooks are accessible to users in a secure manner. Blockchain technology, with its inherent security and transparency, offers a solution to these challenges. By leveraging the Stellar blockchain for tracking assets (ebooks) and Soroban smart contracts for managing access to those assets, we can create a system for managing eBook lending that ensures that only one person at a time can read that eBook and that access is revoked once the lending period ends or the ebook is “returned”.

Objectives

  1. Develop a system to manage eBook lending using Stellar blockchain.
  2. Ensure that eBooks can only be checked out by one person at a time.
  3. Revoke access to eBooks once they are returned or the lending period expires.
  4. Utilize Soroban smart contracts to automate and secure the lending process.

This document describes the various components required to accomplish this. Our goal is to enable a process where ebook ownership, in terms of copyright law, mirrors that of physical books and provides a technological solution that can allow the First-Sale Doctrine be maintained.

Detailed Instructions for Creating a Stellar Asset for a Book

Step 1: Set Up the Stellar Development Environment

  1. Install Node.js and npm:
  2. Install the Stellar SDK:

Step 2: Create a Keypair for the Issuing Account

Generate a keypair for the issuing account that will create and manage the book assets.

const { Keypair } = require('stellar-sdk');

// Generate a keypair for the issuing account
const issuingKeypair = Keypair.random();

console.log('Issuing Account Keypair:');
console.log('Public Key:', issuingKeypair.publicKey());
console.log('Secret Key:', issuingKeypair.secret());

Step 3: Fund the Issuing Account

Use the Stellar testnet friendbot to fund the issuing account.

const fetch = require('node-fetch');

async function fundAccount(publicKey) {
    const response = await fetch(`https://friendbot.stellar.org?addr=${publicKey}`);
    const responseJSON = await response.json();
    console.log('Friendbot response:', responseJSON);
}

fundAccount(issuingKeypair.publicKey());

Step 4: Create and Issue the Book Asset

Create a new asset representing the book and issue it on the Stellar network.

const { Server, TransactionBuilder, Operation, Asset, Networks } = require('stellar-sdk');

// Set up the Stellar server
const server = new Server('https://horizon-testnet.stellar.org');

// Create a new asset representing the book
const bookAsset = new Asset('BookToken', issuingKeypair.publicKey());

async function createBookAsset() {
    // Load the issuing account
    const account = await server.loadAccount(issuingKeypair.publicKey());

    // Create the transaction to issue the asset and set the custom properties
    const transaction = new TransactionBuilder(account, {
        fee: await server.fetchBaseFee(),
        networkPassphrase: Networks.TESTNET,
    })
        .addOperation(Operation.changeTrust({
            asset: bookAsset,
            source: issuingKeypair.publicKey(),
        }))
        .addOperation(Operation.payment({
            destination: issuingKeypair.publicKey(),
            asset: bookAsset,
            amount: '0.0000001', // Minimum amount to create the asset
        }))
        .addOperation(Operation.manageData({
            name: 'isCheckedOut',
            value: 'false',
            source: issuingKeypair.publicKey(),
        }))
        .addOperation(Operation.manageData({
            name: 'checkedOutBy',
            value: '',
            source: issuingKeypair.publicKey(),
        }))
        .setTimeout(100)
        .build();

    // Sign and submit the transaction
    transaction.sign(issuingKeypair);
    await server.submitTransaction(transaction);

    console.log('Book asset created with isCheckedOut and checkedOutBy properties set');
}

createBookAsset();

Step 5: Create a Soroban Smart Contract to Manage the isCheckedOut Property and Store the Request Sender Address

We then create a Soroban smart contract to manage the isCheckedOut property of the book asset and store the address of the request sender.

#![no_std]

use soroban_sdk::{contractimpl, Env, Symbol, Address, Bytes, BytesN, Timestamp};

pub struct LibraryContract;

#[contractimpl]
impl LibraryContract {
    pub fn check_out_book(env: Env, book_id: Symbol, user: Address, return_date: Timestamp) -> BytesN<32> {
        let is_checked_out = env.storage().has(&book_id);

        if is_checked_out {
            let checked_out_status: bool = env.storage().get(&book_id).unwrap().unwrap();
            if checked_out_status {
                panic!("Book is already checked out");
            }
        }

        // Update the status to checked out
        env.storage().set(&book_id, &true);

        // Store the address of the user who checked out the book
        let checked_out_by_key = Symbol::from_str("checkedOutBy");
        env.storage().set(&checked_out_by_key, &user);

        // Store the return date
        let return_date_key = Symbol::from_str("returnDate");
        env.storage().set(&return_date_key, &return_date);

        // Generate a new token (e.g., a unique identifier for this checkout session)
        let new_token = env.random().generate();
        let token_key = Symbol::from_str("checkoutToken");
        env.storage().set(&token_key, &new_token);

        new_token
    }

    pub fn return_book(env: Env, book_id: Symbol, user: Address) {
        let is_checked_out = env.storage().has(&book_id);

        if !is_checked_out {
            panic!("Book does not exist");
        }

        let checked_out_status: bool = env.storage().get(&book_id).unwrap().unwrap();
        if !checked_out_status {
            panic!("Book is already returned");
        }

        // Verify the user returning the book
        let checked_out_by_key = Symbol::from_str("checkedOutBy");
        let stored_user: Address = env.storage().get(&checked_out_by_key).unwrap().unwrap();
        if stored_user != user {
            panic!("Unauthorized return");
        }

        // Update the status to returned
        env.storage().set(&book_id, &false);

        // Clear the checkout token
        let token_key = Symbol::from_str("checkoutToken");
        env.storage().remove(&token_key);
    }

    pub fn verify_token(env: Env, token: BytesN<32>) -> bool {
        let token_key = Symbol::from_str("checkoutToken");
        let stored_token: BytesN<32> = env.storage().get(&token_key).unwrap().unwrap();
        stored_token == token
    }

    pub fn invalidate_token(env: Env, book_id: Symbol) {
        let return_date_key = Symbol::from_str("returnDate");
        let stored_return_date: Timestamp = env.storage().get(&return_date_key).unwrap().unwrap();
        
        if env.now() > stored_return_date {
            // Update the status to returned
            env.storage().set(&book_id, &false);

            // Clear the checkout token
            let token_key = Symbol::from_str("checkoutToken");
            env.storage().remove(&token_key);
        }
    }
}

Step 6: Encrypt the eBook with the Generated Token

We use the generated token to encrypt the eBook. This ensures that only the user with the valid token can decrypt and read the eBook.

const crypto = require('crypto');

// Encrypt the eBook
function encryptEBook(eBookContent, token) {
    const algorithm = 'aes-256-ctr';
    const cipher = crypto.createCipheriv(algorithm, token, Buffer.alloc(16, 0));
    const encrypted = Buffer.concat([cipher.update(eBookContent), cipher.final()]);
    return encrypted;
}

// Decrypt the eBook
function decryptEBook(encryptedEBook, token) {
    const algorithm = 'aes-256-ctr';
    const decipher = crypto.createDecipheriv(algorithm, token, Buffer.alloc(16, 0));
    const decrypted = Buffer.concat([decipher.update(encryptedEBook), decipher.final()]);
    return decrypted;
}

// Example usage
const eBookContent = Buffer.from('This is the content of the eBook');
const token = crypto.randomBytes(32); // This should be the token generated by the smart contract

const encryptedEBook = encryptEBook(eBookContent, token);
console.log('Encrypted eBook:', encryptedEBook);

const decryptedEBook = decryptEBook(encryptedEBook, token);
console.log('Decrypted eBook:', decryptedEBook.toString());

Step 7: Integrate with the Smart Contract

  1. Checkout:
  2. Return:
  3. Verification:
  4. Auto-Invalidate:

TL;DR

By leveraging the Stellar blockchain and Soroban smart contracts, we can create a secure and efficient system for managing eBook lending in libraries.

This proposed system adds books to the Stellar blockchain as assets with custom properties and deploys a checkout wasm contract that ensures that only one person at a time can check out a particular copy of an eBook by encrypting the ebook file delivered to the user with a key specific to this particular checkout. This key is revoked once the book is returned or the lending period expires. This approach not only enhances the security and transparency of the lending process but also aligns with the goals of preserving intellectual property rights and providing seamless access to digital resources.