Skip to content

Commit

Permalink
DID: Decentralized identifiers (DIDs) (XLS-40): (#4636)
Browse files Browse the repository at this point in the history
Implement native support for W3C DIDs.

Add a new ledger object: `DID`.

Add two new transactions:
1. `DIDSet`: create or update the `DID` object.
2. `DIDDelete`: delete the `DID` object.

This meets the requirements specified in the DID v1.0 specification
currently recommended by the W3C Credentials Community Group.

The DID format for the XRP Ledger conforms to W3C DID standards.
The objects can be created and owned by any XRPL account holder.
The transactions can be integrated by any service, wallet, or application.
  • Loading branch information
mvadari authored Oct 18, 2023
1 parent 41cd337 commit b421945
Show file tree
Hide file tree
Showing 30 changed files with 1,063 additions and 7 deletions.
3 changes: 3 additions & 0 deletions Builds/CMake/RippledCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/CreateTicket.cpp
src/ripple/app/tx/impl/DeleteAccount.cpp
src/ripple/app/tx/impl/DepositPreauth.cpp
src/ripple/app/tx/impl/DID.cpp
src/ripple/app/tx/impl/Escrow.cpp
src/ripple/app/tx/impl/InvariantCheck.cpp
src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp
Expand Down Expand Up @@ -785,6 +786,7 @@ if (tests)
src/test/app/DeliverMin_test.cpp
src/test/app/DepositAuth_test.cpp
src/test/app/Discrepancy_test.cpp
src/test/app/DID_test.cpp
src/test/app/DNS_test.cpp
src/test/app/Escrow_test.cpp
src/test/app/FeeVote_test.cpp
Expand Down Expand Up @@ -937,6 +939,7 @@ if (tests)
src/test/jtx/impl/check.cpp
src/test/jtx/impl/delivermin.cpp
src/test/jtx/impl/deposit.cpp
src/test/jtx/impl/did.cpp
src/test/jtx/impl/envconfig.cpp
src/test/jtx/impl/fee.cpp
src/test/jtx/impl/flags.cpp
Expand Down
226 changes: 226 additions & 0 deletions src/ripple/app/tx/impl/DID.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/app/tx/impl/DID.h>

#include <ripple/basics/Log.h>
#include <ripple/ledger/ApplyView.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>

namespace ripple {

/*
DID
======
Decentralized Identifiers (DIDs) are a new type of identifier that enable
verifiable, self-sovereign digital identity and are designed to be
compatible with any distributed ledger or network. This implementation
conforms to the requirements specified in the DID v1.0 specification
currently recommended by the W3C Credentials Community Group
(https://www.w3.org/TR/did-core/).
*/

//------------------------------------------------------------------------------

NotTEC
DIDSet::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureDID))
return temDISABLED;

if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

if (!ctx.tx.isFieldPresent(sfURI) &&
!ctx.tx.isFieldPresent(sfDIDDocument) && !ctx.tx.isFieldPresent(sfData))
return temEMPTY_DID;

if (ctx.tx.isFieldPresent(sfURI) && ctx.tx[sfURI].empty() &&
ctx.tx.isFieldPresent(sfDIDDocument) && ctx.tx[sfDIDDocument].empty() &&
ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty())
return temEMPTY_DID;

auto isTooLong = [&](auto const& sField, std::size_t length) -> bool {
if (auto field = ctx.tx[~sField])
return field->length() > length;
return false;
};

if (isTooLong(sfURI, maxDIDURILength) ||
isTooLong(sfDIDDocument, maxDIDDocumentLength) ||
isTooLong(sfData, maxDIDAttestationLength))
return temMALFORMED;

return preflight2(ctx);
}

TER
addSLE(
ApplyContext& ctx,
std::shared_ptr<SLE> const& sle,
AccountID const& owner)
{
auto const sleAccount = ctx.view().peek(keylet::account(owner));
if (!sleAccount)
return tefINTERNAL;

// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve =
ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);

if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}

// Add ledger object to ledger
ctx.view().insert(sle);

// Add ledger object to owner's page
{
auto page = ctx.view().dirInsert(
keylet::ownerDir(owner), sle->key(), describeOwnerDir(owner));
if (!page)
return tecDIR_FULL;
(*sle)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
ctx.view().update(sleAccount);

return tesSUCCESS;
}

TER
DIDSet::doApply()
{
// Edit ledger object if it already exists
Keylet const didKeylet = keylet::did(account_);
if (auto const sleDID = ctx_.view().peek(didKeylet))
{
auto update = [&](auto const& sField) {
if (auto const field = ctx_.tx[~sField])
{
if (field->empty())
{
sleDID->makeFieldAbsent(sField);
}
else
{
(*sleDID)[sField] = *field;
}
}
};
update(sfURI);
update(sfDIDDocument);
update(sfData);

if (!sleDID->isFieldPresent(sfURI) &&
!sleDID->isFieldPresent(sfDIDDocument) &&
!sleDID->isFieldPresent(sfData))
{
return tecEMPTY_DID;
}
ctx_.view().update(sleDID);
return tesSUCCESS;
}

// Create new ledger object otherwise
auto const sleDID = std::make_shared<SLE>(didKeylet);
(*sleDID)[sfAccount] = account_;

auto set = [&](auto const& sField) {
if (auto const field = ctx_.tx[~sField]; field && !field->empty())
(*sleDID)[sField] = *field;
};

set(sfURI);
set(sfDIDDocument);
set(sfData);

return addSLE(ctx_, sleDID, account_);
}

NotTEC
DIDDelete::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureDID))
return temDISABLED;

if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;

if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;

return preflight2(ctx);
}

TER
DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
{
auto const sle = ctx.view().peek(sleKeylet);
if (!sle)
return tecNO_ENTRY;

return DIDDelete::deleteSLE(ctx.view(), sle, owner, ctx.journal);
}

TER
DIDDelete::deleteSLE(
ApplyView& view,
std::shared_ptr<SLE> sle,
AccountID const owner,
beast::Journal j)
{
// Remove object from owner directory
if (!view.dirRemove(
keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
{
JLOG(j.fatal()) << "Unable to delete DID Token from owner.";
return tefBAD_LEDGER;
}

auto const sleOwner = view.peek(keylet::account(owner));
if (!sleOwner)
return tecINTERNAL;

adjustOwnerCount(view, sleOwner, -1, j);
view.update(sleOwner);

// Remove object from ledger
view.erase(sle);
return tesSUCCESS;
}

TER
DIDDelete::doApply()
{
return deleteSLE(ctx_, keylet::did(account_), account_);
}

} // namespace ripple
73 changes: 73 additions & 0 deletions src/ripple/app/tx/impl/DID.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#ifndef RIPPLE_TX_DID_H_INCLUDED
#define RIPPLE_TX_DID_H_INCLUDED

#include <ripple/app/tx/impl/Transactor.h>

namespace ripple {

class DIDSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
{
}

static NotTEC
preflight(PreflightContext const& ctx);

TER
doApply() override;
};

//------------------------------------------------------------------------------

class DIDDelete : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};

explicit DIDDelete(ApplyContext& ctx) : Transactor(ctx)
{
}

static NotTEC
preflight(PreflightContext const& ctx);

static TER
deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner);

static TER
deleteSLE(
ApplyView& view,
std::shared_ptr<SLE> sle,
AccountID const owner,
beast::Journal j);

TER
doApply() override;
};

} // namespace ripple

#endif
15 changes: 15 additions & 0 deletions src/ripple/app/tx/impl/DeleteAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
//==============================================================================

#include <ripple/app/tx/impl/DID.h>
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/SetSignerList.h>
Expand Down Expand Up @@ -133,6 +134,18 @@ removeNFTokenOfferFromLedger(
return tesSUCCESS;
}

TER
removeDIDFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DIDDelete::deleteSLE(view, sleDel, account, j);
}

// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
Expand All @@ -151,6 +164,8 @@ nonObligationDeleter(LedgerEntryType t)
return removeDepositPreauthFromLedger;
case ltNFTOKEN_OFFER:
return removeNFTokenOfferFromLedger;
case ltDID:
return removeDIDFromLedger;
default:
return nullptr;
}
Expand Down
1 change: 1 addition & 0 deletions src/ripple/app/tx/impl/InvariantCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ LedgerEntryTypesMatch::visitEntry(
case ltBRIDGE:
case ltXCHAIN_OWNED_CLAIM_ID:
case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
case ltDID:
break;
default:
invalidTypeAdded_ = true;
Expand Down
5 changes: 5 additions & 0 deletions src/ripple/app/tx/impl/applySteps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <ripple/app/tx/impl/CreateCheck.h>
#include <ripple/app/tx/impl/CreateOffer.h>
#include <ripple/app/tx/impl/CreateTicket.h>
#include <ripple/app/tx/impl/DID.h>
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/Escrow.h>
Expand Down Expand Up @@ -154,6 +155,10 @@ with_txn_type(TxType txnType, F&& f)
return f.template operator()<XChainAddAccountCreateAttestation>();
case ttXCHAIN_ACCOUNT_CREATE_COMMIT:
return f.template operator()<XChainCreateAccountCommit>();
case ttDID_SET:
return f.template operator()<DIDSet>();
case ttDID_DELETE:
return f.template operator()<DIDDelete>();
default:
throw UnknownTxnType(txnType);
}
Expand Down
Loading

0 comments on commit b421945

Please sign in to comment.