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”.
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.
node -v
and
npm -v
in your terminal.Run the following command to install the Stellar SDK:
npm install stellar-sdk
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());
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());
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
.sign(issuingKeypair);
transactionawait server.submitTransaction(transaction);
console.log('Book asset created with isCheckedOut and checkedOutBy properties set');
}
createBookAsset();
isCheckedOut
Property and Store the Request Sender
AddressWe 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
.storage().set(&book_id, &true);
env
// Store the address of the user who checked out the book
let checked_out_by_key = Symbol::from_str("checkedOutBy");
.storage().set(&checked_out_by_key, &user);
env
// Store the return date
let return_date_key = Symbol::from_str("returnDate");
.storage().set(&return_date_key, &return_date);
env
// 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");
.storage().set(&token_key, &new_token);
env
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
.storage().set(&book_id, &false);
env
// Clear the checkout token
let token_key = Symbol::from_str("checkoutToken");
.storage().remove(&token_key);
env}
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();
== token
stored_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
.storage().set(&book_id, &false);
env
// Clear the checkout token
let token_key = Symbol::from_str("checkoutToken");
.storage().remove(&token_key);
env}
}
}
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());
check_out_book
function to get the token.return_book
function to invalidate the
token.verify_token
function to check if the token is
valid before allowing the user to decrypt and read the eBook.invalidate_token
function to
check if the return date has passed and invalidate the token if
necessary.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.