uPort implements a simple yet general purpose decentralized PKI system, making it easy to create and verify off-chain JWT messages.
We need a decentralized way to lookup public keys that can be used to verify off-chain JWTs. This allows us to use the power of the Ethereum blockchain to verify signed data privately transferred between parties.
The PKI is not needed for blockchain transactions themselves, as any blockchain already has a PKI-like functionality built in.
We are primarily using it with JWTs, although it could be used for signing other data formats as well.
The following overview shows the basic process for creating and verifying a trusted off-chain transaction between two parties using the uPort PKI.
The Identity Document is a JSON LD document stored on IPFS. Here is an example:
{
"@context":"http://schema.org",
"@type":"Person","publicKey":"0x04848b547c6effe251b6e9f69c3bc6845b7997963554703aa41bc1b4c8d8db787ac966938139d5b36f404b89727fbc279153a20ad43ff25da0c30edb8b84d9c836",
"publicEncKey":"bpEGZfAtubOkFSsIdZFSlMN30hYlNOjHzS7LJgep82A="
}
Since it is a JSON LD document, you can include all kinds of data (such as name, location, etc.)
The most important part for the function of the PKI itself is that it contains the publicKey
of the identity. This is the only required item.
This is a 0x
prefixed hex encoded DER encoded public key for the secp256k1 ECDSA curve.
This is an encryption public key created for use with NACL Box Public Key Encryption.
The public encryption key generated by the NACL library is encoded as a Base64 string.
The uPort app and other apps presenting signed JWTs to their users will use the name
item stored in the Identity Document.
The uPort app and other apps presenting signed JWTs to their users will use the image
item stored in the Identity Document.
The image currently has to be stored in IPFS and the format should look like this:
{"image":{"contentUrl":"/ipfs/HASH"}}
Any Signed Message has an iss
attribute. This contains an MNID.
An MNID contains an Ethereum address, the network id as well as a checksum.
- Decode the MNID of the
iss
and extract thenetwork
and theaddress
- In the uport-registry for the
network
call the functionget("uPortProfileIPFS1220", address, address)
which returns a hash value encoded as 32 bytes - Encode the IPFS hash by prepending hex
1220
to the 32 byte hash and encoding it as base58 - Fetch JSON Identity Document from IPFS using IPFS hash
- Public Key is stored in the
publicKey
key of the Identity Document
Done in the same way as above except for the last step:
- Public Key is stored in the
publicEncKey
key of the Identity Document
Any address on any supported Ethereum blockchain can register its identity document on the uport-registry.
This shows the basic process:
- Generate a Key Pair
- Create a Identity Document containing the Public Key
- Publish Identity Document to IPFS
- Decode IPFS hash returned to get the raw 32 byte hash value
- Pick an Ethereum network to register your identity on
- Generate the Ethereum address for your Key Pair
- Create a transaction in the uport-registry for the
network
for the functionset("uPortProfileIPFS1220", address, hash)
signed by your Key Pair
Smart contracts can't sign on their own, so a signing Key Pair will need to be created first.
- Generate a Key Pair
- Create an Identity Document containing the Public Key
- Publish Identity Document to IPFS
- Decode IPFS hash returned to get the raw 32 byte hash value
- With your smart contract code create an internal transaction to the uport-registry on the
network
that your smart contract is deployed to for the functionset("uPortProfileIPFS1220", address, hash)
Here is an example of how to register an Identity for your smart contract in Solidity:
contract Registry { function set(bytes32 key, address subject, bytes32 value); }
contract MyContract {
address public owner;
Registry registry;
function MyContract(address _registry) {
owner = msg.sender;
registry = Registry(_registry);
}
function setIdentityDoc(bytes32 hash) {
// Only allow owner of contract to set the identity document.
// There could of course be more advanced governance mechanisms here.
require(msg.sender == owner);
registry.set("uPortProfileIPFS1220", this, hash);
}
}
Identities created by the uPort Mobile App consist of a simple Proxy smart contract controlled by a flexible access control smart contract that we call the IdentityManager.
This structure allows us to create recoverable identities controlled by multiple devices and even allows us to safely upgrade the complex access control logic.
The way an identity is created in the Mobile App is as follows:
- Generate a Key Pair on your uPort app
- Pick an Ethereum network to register your identity on
- Create an Ethereum transaction registering a Proxy using the IdentityManager
- Create an Identity Document containing the Public Key
- Publish Identity Document to IPFS
- Decode IPFS hash returned to get the raw 32 byte hash value
- Create a transaction on the IdentityManager that forwards a transaction to the uport-registry calling the function
set("uPortProfileIPFS1220", address, hash)
signed by your Key Pair