OVIP: 3
Title: VASP Identity
Author: David Riegelnig <[email protected]>
Discussions-To: https://community.openvasp.org/#narrow/stream/21-protocol-.2F.20ovip
Status: Accepted
Type: Standard
Created: 2020-05-08
The OpenVASP protocol uses Ethereum smart contracts as its public key infrastructure.
Each Virtual Assets Service Provider (VASP) deploys its own VASP Contract that represents the identity of the VASP and that stores the public keys required by the protocol.
VASP Contracts are deployed via a universal smart contract factory, the Index Contract, which maintains a register of all deployed VASP Contracts.
When a VASP Contract is deployed, the VASP can specify a unique VASP Code as an identifier, which can henceforth be used to retrieve the VASP Contract from the Index Contract.
This OVIP specifies the interface of the VASP Contract and the Index Contract, which together set the rules how a VASP can establish its identity within the OpenVASP protocol. It is a revision of the initial specification proposed in the OpenVASP White Paper.
The signingKey, messageKey and the transportKey must be stored in compressed form. That is it must be represented with exactly 33 bytes and is prefixed with '02' or '03'. This format must be enforced by the smart contract, any call changing the key to a byte array in invalid format must fail.
The VASP Code consists of 8 hexadecimal characters and is a unique identifier for the VASP Contract representing the VASP's identity.
It can be freely chosen by the VASP out of all possible combinations still available when the VASP Contract is deployed (see sections below).
The Index Contract implements the Addressing Layer as described in ovip-0009. There can be multiple implementations of this layer and even multiple istances of the Index Contract.
To ensure global uniqueness, the VASP Code Type specifies for which implementation of the Addressing Layer the VASP Code is registered (see ovip-0002).
The combination of the VASP Code Type and the VASP Code globally, uniquely identifies a VASP and is referred to as the VASP Identifier.
The Index Contract serves as a universal smart contract factory and is deployed only once by the OpenVASP Association. The owner of the contract merely has the ability to halt or terminate the contract in emergency situations or if required by protocol upgrades.
function createVASPContract
(
bytes4 vaspCode,
address owner,
bytes4 channels,
bytes calldata transportKey,
bytes calldata messageKey,
bytes calldata signingKey
)
external
returns (address)
Description:
Deploys a VASP Contract, owned by owner
and returns the address of the deployed contract. The Index Contract records the deployed contract's address under the specified vaspCode
in its internal mapping. The channels, transportKey, messageKey and signingKey respectively are set on deployment of the VASP contract.
Conditions:
owner
must be a valid Ethereum address; and
vaspCode
must be unique in the internal mapping of the Index Contract, that is, vaspCode
must have never been used in a successful call of this function before.
Public Key Format must be enforced for transportKey, messageKey and signingKey.
Use Case:
Called by a VASP to deploy a VASP Contract that represents the identity of the VASP within the OpenVASP protocol and that is mapped to a unique VASP Code chosen by the VASP.
Event log:
VASPContractCreated(bytes4 indexed vaspCode, address indexed vaspAddress)
ChannelsChanged(bytes4 indexed vaspCode, bytes4 previousChannels, bytes4 newChannels);
TransportKeyChanged(bytes4 indexed vaspCode, bytes previousTransportKey, bytes newTransportKey);
MessageKeyChanged(bytes4 indexed vaspCode, bytes previousMessageKey, bytes newMessageKey);
SigningKeyChanged(bytes4 indexed vaspCode, bytes previousSigningKey, bytes newSigningKey);
function getVASPAddressByCode
(
bytes4 vaspCode
)
external view
returns (address)
Description:
Returns the address of the VASP Contract which has been recorded under the specified vaspCode
. If no entry is found in the internal mapping, an empty address (0x) is returned by the function.
Use Case:
Called to get the VASP Contract which is uniquely assigned to a specific VASP Code. Intended method to retrieve a VASP Contract.
function getVASPCodeByAddress
(
address vaspAddress
)
external view
returns (bytes4)
Description:
Returns the VASP Code that was assigned to the specified vaspAddress
by the Index Contract. If no entry is found in the internal mapping, an empty address (0x) is returned by the function.
Use Case:
Called to get the VASP Code which is uniquely mapped to a given VASP Contract address.
function pause()
external
Description:
Disables createVASPContract functionality for all callers and arguments.
Access Can be called only by the owner
Use Case:
Pauses the lifecycle of the contract while preserving the current mapping and allowing queries towards existing entries.
function unpause()
external
Description:
Re-enables createVASPContract functionality for all callers and arguments.
Access Can be called only by the owner
Use Case:
Resumes the lifecycle of the contract.
function terminate
(
address payable recipient
)
external
Description:
Frees memory allocated by the contract and transfers all potentially accidentally funds send to the contract to recipient.
Use Case:
End the lifecycle of the VASP Index without preserving the existing mapping. Invalidates and obsoletes existing entries.
function transferOwnerRole
(
address newOwnerCandidate
)
external
Description:
Sets the specified address as a contract's owner candidate.
Access Can be called only by the owner
Use Case:
Called by the contract owner to prepare the transfer of the contract's ownership to a new owner.
Event log:
OwnerRoleTransferStarted(address indexed currentOwner, address indexed newOwnerCandidate);
function cancelOwnerRoleTransfer()
external
Description:
Cancels ownership transfer.
Access Can be called only by the owner
Use Case:
Called by the contract owner to cancel the transfer of the contract's ownership to a new owner.
Event log:
OwnerRoleTransferCancelled();
function acceptOwnerRole()
external
Description:
Changes the owner of the contract.
Access Can be called only by the new owner candidate
Use Case:
Called by the future owner in order to accept transfer of the contract's ownership.
Event log:
OwnerRoleTransferCompleted(address indexed previousOwner, address indexed newOwner);
function renounceOwnerRole()
external
Description:
Renounces the owner role
Access Can be called only by the owner
Use Case:
Called by the future owner in order to accept transfer of the contract's ownership.
Use Case: Can be called to leave the contract without owner. It will not be possible to call functions callable only by the owner anymore.
Each VASP participating in the OpenVASP protocol deploys and owns a dedicated VASP Contract, representing the VASP's identity.
The VASP Contract is used to provide the public keys required to decrypt and verify protocol messages that were signed and sent by the VASP.
VASP Contracts can only be deployed together with a unique VASP Code and via the Index Contract, which keeps track of all deployed VASP Contracts and their respective VASP Code. Each VASP Contract can always be retrieved from the Index Contract by the associated VASP Code.
The owner of the VASP Contract can change the public keys and can transfer ownership.
function channels()
external view
returns (bytes4)
Description:
Returns the value of the channels.
function signingKey()
external view
returns (bytes)
Description:
Returns the value of the signing key.
function messageKey()
external view
returns (bytes)
Description:
Returns the value of the message key.
function transportKey()
external view
returns (bytes)
Description:
Returns the value of the transport key.
setChannels
(
bytes4 newChannels
)
external
Description:
Sets the contract's channels.
Access:
Can be called only by the owner.
Use Case:
Owner changes the communication channels the VASP is accepting for OpenVASP messages. channels
is to be interpreted as a bit mask, where each bit indicates a specific channel. At this time, the following channels are available:
Bit Mask | Channel |
---|---|
00000000 000000000 00000000 00000001 | Ethereum Whisper as specified per ovip-0010 |
Event log:
ChannelsChanged(bytes8 indexed vaspCode, bytes4 previousChannels, bytes4 newChannels);
TransportKeyChanged(bytes8 indexed vaspCode, string previousTransportKey, string newTransportKey);
MessageKeyChanged(bytes8 indexed vaspCode, string previousMessageKey, string newMessageKey);
SigningKeyChanged(bytes8 indexed vaspCode, string previousSigningKey, string newSigningKey);
function setTransportKey
(
bytes calldata newTransportKey
)
external
Description:
Changes the contract's transport key. Public Key Format must be enforced for newTransportKey.
Access:
Can be called only by the owner.
Use Case:
Owner changes the public key required to secure communication on the transport layer (e.g. encryption of Whisper envelopes).
Event log:
TransportKeyChanged(bytes4 indexed vaspCode, bytes previousTransportKey, bytes newTransportKey);
function setMessageKey
(
bytes calldata newMessageKey
)
external
Description:
Changes the contract's message key. Public Key Format must be enforced for newMessageKey.
Access:
Can be called only by the owner.
Use Case:
Owner changes the public key required to encrypt OpenVASP messages (session layer).
Event log:
MessageKeyChanged(bytes4 indexed vaspCode, bytes previousMessageKey, bytes newMessageKey);
function setSigningKey
(
bytes calldata newSigningKey
)
external
Description:
Changes the contract's signing key. Public Key Format must be enforced for newSigningKey.
Access:
Can be called only by the owner.
Use Case:
Owner changes the public key required to verify message signatures.
Event log:
SigningChanged(bytes4 indexed vaspCode, string previousMessageKey, string newMessageKey);
function transferOwnerRole
(
address newOwnerCandidate
)
external
Description:
Sets the specified address as a contract's owner candidate.
Access Can be called only by the owner
Use Case:
Called by the contract owner to prepare the transfer of the contract's ownership to a new owner.
Event log:
OwnerRoleTransferStarted(address indexed currentOwner, address indexed newOwnerCandidate);
function cancelOwnerRoleTransfer()
external
Description:
Cancels ownership transfer.
Access Can be called only by the owner
Use Case:
Called by the contract owner to cancel the transfer of the contract's ownership to a new owner.
Event log:
OwnerRoleTransferCancelled();
function acceptOwnerRole()
external
Description:
Changes the owner of the contract.
Access Can be called only by the new owner candidate
Use Case:
Called by the future owner in order to accept transfer of the contract's ownership.
Event log:
OwnerRoleTransferCompleted(address indexed previousOwner, address indexed newOwner);
function renounceOwnerRole()
external
Description:
Renounces the owner role
Access Can be called only by the owner
Use Case:
Called by the future owner in order to accept transfer of the contract's ownership.
Use Case: Can be called to leave the contract without owner. It will not be possible to call functions callable only by the owner anymore.
Predicting the future is fairly difficult. However we must assume that a situation arises which demands a change to the VASP Index in one form or another. We see two scenarios. One where the VASP Contracts become obsolete and another where they could be reused by the new Index contract. In both cases we would like to be able to reserve previously reserved VASP Codes. This is not a technically required feature as the VASP Code Type of the VASP Identifier will change in any case but will provide more clarity. The blueprint for achieving this would be to have a new Index contract call the exisiting one to disallow reusage of existing VASP Codes unless the caller is the owner of the VASP Contract which is mapped to the VASP Code in the obsolete VASP Index. If possible it would then also be easy to point the new index entry towards the existing VASP contract, reducing redundancy and gas cost.
- Community feedback -- This OVIP reflects community feedback on the initial VASP Contract specification suggested in the OpenVASP White Paper.
- Clarity -- OpenVASP implementation teams need clarity about the VASP identity mechanism to complete a first version of interoperable implementations.
The specification maintains its decentralized approach by allowing the VASP to self-assign a VASP Code and deploy the VASP Contract without having to go through a central authority.
One of the key changes to the initial design proposal is the reduction of the VASP Contract to a minimal record for public keys. This is done for the following reasons:
- Separating identity from verification -- The protocol's trust model has always seen identity verification by trusted third parties as an additional layer on top of peer-to-peer trust. Consequently, identity attributes (such as name, address, etc.) are better stored where these attributes are actually verified. Omitting identity attributes from the self-assigned VASP Contract on the other hand avoids any false sense of security from these unverified properties.
- Simplicity -- The revised specification simplifies smart contract design.
- Interoperability -- A reduced VASP Contract together with the layered approach to identity verification allows for easier interoperability with other mechanisms for identity management, e.g. used by other travel rule solutions.
Maintaining the VASP Code as an identifier for the VASP Contract is done for practicality in (human) processing and its use in the Virtual Assets Account Number (VAAN). See OVIP-2 for more details about the VAAN.
The VASP Code is not stored in the VASP Contract, but only in the internal mapping of the Index Contract. In this way, developers interacting with these smart contracts are constantly reminded to always retrieve the VASP Contract from the source (Index Contract) by the help of the getVaspByCode()
function.
The Index Contract It has no owner with special privileges to rule out any unwanted influence on this security-critical component after deployment.
For the VASP Contract, the ability to transfer ownership is considered a must to deal with situations like change of ownership of the VASP entity or a compromised private key.
Separating encryption on transport and session layers will allow for a better security architecture as the internet-facing Whisper could be decoupled from the server handling the sessions. Further, it allows the usage of a Whisper node by different session handlers without giving access to session layers details, particularly originator/beneficiary data.
Ways to verify the VASP's identity will be covered in a separate OVIP.
The specification proposed in this OVIP is not backwards compatible.