This SDK is used not only to create and manage ICON DID
, but also to issue and verify credentials
and presentations
.
This section explains what kind of actions owner
, issuer
, and verifier
typically perform in order to use ICON DID
, and describes how to do them in a simple manner.
An owner
is able to keep his/her personal information on himself/herself without storing it on a remote server, thus keeping it secure.
The steps an owner
must take are described below.
- Generate a DID (See: DID Document)
- Request a credential
- The owner sends a credential request to an
issuer
to claim his credential. (See: Credential Request)'
- The owner sends a credential request to an
- Generate a presentation
- When an
owner
receives a presentation request from averifier
, he responds back with apresentation
. (See: Presentation)
- When an
An issuer
, upon receiving a request from an owner
, verifies the owner
's identity and provides a certificate.
The steps an issuer
must take are described below
- Generate a DID (See: DID Document)
- Verify the credential request
- Upon receiving a credential request from an
owner
, theissuer
verifies the request and validates his claims. (See : Verifying Requests)
- Upon receiving a credential request from an
- Generate credentials
- Upon successful validation, the
issuer
creates and sends the correspondingcredential
to theowner
. (See: Credential)
- Upon successful validation, the
A verifier
is an entity that requires certain information from an owner
in order to provide service.
The steps a verifier
must take are described below.
- Generate a DID (See: DID Document)
- Technically, not all
verifiers
must have its DID. If it doesn't have its DID, you can skip this step
- Technically, not all
- Send a presentation request
- The
verifier
sends a presentation request to anowner
. Theverifier
may optionally sign the request before sending it (See: Presentation request)
- The
- Validate presentation
- The
verifier
validates thepresentation
that anowner
sent, and thecredentials
included inside it. (See: presentation verification)
- The
An ICON DID
is managed on the ICON blockchain, and in order to create a DID and view or update its DID document, a transaction must be sent to a SCORE.
See: ICON DID method specification
This SDK can be used to create transactions that will be sent to a SCORE in order to create a DID and view or update its DID document.
Creates a new DID document.
In order to successfully create a DID document you will have to send a transaction that includes the following information to a SCORE.
- Information about the public key that will registered on the DID document (id, type, key, value)
- Data that is signed by a private key corresponding to the public key (in order to prove that the public key is appropriately owned)
Key providers are objects that store information needed in order to create or modify DID documents
(As of version 0.9.1, SCOREs only support ES256K as the signing algorithm)
// Information about the public key that will be registered on a DID document
String keyId = "ES256K-key";
// Information about the algorithm of the key
Algorithm algorithm = AlgorithmProvider.create(AlgorithmProvider.Type.ES256K);
// Generate a KeyProvider instance
KeyProvider keyProvider = algorithm.generateKeyProvider(keyId);
In order to create a DID document on the blockchain, you must instantiate a DIDService
object.
You also need an IconService
and KeyWallet
instance in order to create a transaction. (Please refer to the documents of the ICON SDK)
IconService iconService = new IconService(new HttpProvider("https://url"));
BigInteger networkId = new BigInteger("1");
Address scoreAddress = new Address("cx000...1");
KeyWallet wallet = KeyWallet.load(new Bytes("hx000...1"));
DidService didService = new DidService(iconService, networkId, scoreAddress);
After creating an instance of DidService
and KeyProvider respectively, you will be able to successfully send a transaction to a SCORE by calling DidService.create
.
(As of version 0.9.1, SCOREs only support Base64 as the EncodeType)
// Encoding type used to encode the string of the public key
PublicKeyProperty.EncodeType encodeType = PublicKeyProperty.EncodeType.BASE64;
// Create parameters that will be used when sending the DID registration request
String param = ScoreParameter.create(keyProvider, encodeType);
// Check results after registering a DID on the SCORE (on success, a DID document will be returned)
Document doc = didService.create(wallet, param);
doc.toJson();
// A DidKeyHolder object must be instantiated in order to use the DID
DidKeyHolder didKeyHolder = new DidKeyHolder.Builder(keyProvider)
.did(document.getId())
.build();
// Store the DidKeyHolder with a keystorefile
Keystore.storeDidKeyHolder(password, didKeyHolder, "did.json");
After the DID is successfully created on the SCORE, the newly-created Document
will be transformed into a JSON string and returned like the example below.
{
"version": "1.0",
"id": "did:icon:01:b2eb749fe08cf8185ae057d73a9ed7f963b4f2e0ae8655bd",
"created": 529,
"publicKey": [{
"id": "ES256K-key",
"type": ["Secp256k1VerificationKey"],
"publicKeyBase64": "BIUG...=",
"created": 529
}],
"authentication": [{
"publicKey": "ES256K-key"
}]
}
View a DID document by calling the readDocument
function of a DidService instance. (See: DidService instantiation)
String did = "did:icon:01:...1";
Document doc = didService.readDocument(did);
Add a public key to the already-created DID document (See: Register DID document, DidService instantiation)
// DID registered on the SCORE
String did = "did:icon:01:...1";
// Create DidKeyHolder to add public key
String authKeyId = "ES256K-key";
String privateKey = "..."; // base64
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
// Either manually
DidKeyHolder didKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
// Or by loading keystorefile
DidKeyHolder didKeyHolder = Keystore.loadDidKeyHolder(password, new File("did.json"));
// Generate new key
String keyId = "newKey";
Algorithm algorithm = AlgorithmProvider.create(AlgorithmProvider.Type.ES256K);
KeyProvider keyProvider = algorithm.generateKeyProvider(keyId);
// Create JWT needed in order to send a DID public key addition request to SCORE
Jwt jwt = ScoreParameter.addKey(
didKeyHolder, keyProvider, EncodeType.BASE64);
// Create signed JWT
String signedJwt = didKeyHolder.sign(jwt);
// Send a public key addition request to SCORE, then check result
Document doc = didService.addPublicKey(wallet, signedJwt));
doc.toJson();
After the successful addition of a public key, the document will be returned in the form of a JSON string like the example below.
{
"version": "1.0",
"id": "did:icon:01:b2eb749fe08cf8185ae057d73a9ed7f963b4f2e0ae8655bd",
"created": 529,
"publicKey": [{
"id": "newKey",
"type": ["Secp256k1VerificationKey"],
"publicKeyBase64": "BMOg...=",
"created": 530
}, {
"id": "ES256K-key",
"type": ["Secp256k1VerificationKey"],
"publicKeyBase64": "BIUG...=",
"created": 529
}],
"authentication": [{
"publicKey": "ES256K-key"
}, {
"publicKey": "newKey"
}],
"updated": 530
}
Revoke a certain public key on a DID document. Doing this will invalidate the public key, thus making it unable to be used any further.
// DID that is registered on the SCORE
String did = "did:icon:01:...1";
// Create DidKeyHolder to revoke public key
String authKeyId = "ES256K-key";
String privateKey = "..."; // base64
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
// Either manually,
DidKeyHolder didKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
// Or by loading keystorefile
DidKeyHolder didKeyHolder = Keystore.loadDidKeyHolder(password, new File("did.json"));
// Id of the public key that we are aiming to revoke
String keyId = "newKey";
// Create JWT in order to revoke a DID public key
Jwt jwt = ScoreParameter.revokeKey(didKeyHolder, keyId);
String signedJwt = didKeyHolder.sign(jwt);
// Send a public key revocation request to SCORE, then check the result
Document doc = didService.revokeKeyJwt(wallet, signedJwt);
doc.toJson();
Upon a successful revocation of the public key, the document will be returned in the format below.
{
"version": "1.0",
"id": "did:icon:01:b2eb749fe08cf8185ae057d73a9ed7f963b4f2e0ae8655bd",
"created": 529,
"publicKey": [{
"id": "newKey",
"type": ["Secp256k1VerificationKey"],
"publicKeyBase64": "BMOg...=",
"created": 530,
"revoked": 531
}, {
"id": "ES256K-key",
"type": ["Secp256k1VerificationKey"],
"publicKeyBase64": "BIUG...=",
"created": 529
}],
"authentication": [{
"publicKey": "ES256K-key"
}],
"updated": 531
}
After an issuer
validates the claims of an owner
, the owner
can acquire the credential
of the claims.
In order to acquire a credential
, an owner
must send the following JWT to an issuer
.
The DID used in this process must be already registered on the blockchain (See: DID Document Registration, DidService object instantiation)
The following example creates a DidClaim
object and issues a JWT.
// DID of owner
String did = "did:icon:01:...1";
// DID of issuer
String issuerDid = "did:icon:01:...1";
// DID version
String version = "1.0";
// KeyId information of the public key that is registered on the DID document
String keyId = "owner";
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K;
// Private key corresponding to the public key
String privateKey = "..."; // base64
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
DidKeyHolder ownerKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
// Claim type of credential that will be requested
Map claims = new HashMap();
claims.put("email", "abc@icon.foundation");
// Generate random nonce to use during request
String nonce = Hex.toHexString(AlgorithmProvider.secureRandom().generateSeed(4));
// Build instance of ClaimRequest
ClaimRequest request = new ClaimRequest.Builder(ClaimRequest.Type.CREDENTIAL)
.didKeyHolder(ownerKeyHolder)
.requestClaims(claims)
.responseId(issuerDid)
.nonce(nonce) // (optional)
.version(version)
.build();
String requestJwt = ownerKeyHolder.sign(request.getJwt());
The created JWT is as follows. (See JWT debugger)
// header
{
"alg": "ES256K",
"kid": "did:icon:01:e96721825d09683be1438800e976ab498a0cf4fafca29316#owner"
}
// payload
{
"version": "1.0",
"iat": 1553582482,
"iss": "did:icon:01:961b6cd64253fb28c9b0d3d224be5f9b18d49f01da390f08",
"requestClaim": {
"email": "abc@icon.foundation"
},
"aud": "did:icon:01:961b6cd64253fb28c9b0d3d224be5f9b18d49f01da390f08",
"type": [
"REQ_CREDENTIAL",
"email"
]
}
// signature
The request token created by an owner
is validated using the method below. (See: Credential Request)
The public key of the owner is required in the process of validating the token, and can be acquired by querying the blockchain. (See: DidService instantiation)
// JWT received from the owner
String token = "eyJ0eXA..";
// Using the token, create an instance of ClaimRequest
ClaimRequest claimRequest = ClaimRequest.valueOf(token);
logger.debug("REQ_CREDENTIAL Info");
logger.debug(" type : {}", claimRequest.getTypes());
logger.debug(" claims : {}", claimRequest.getClaims());
logger.debug(" requestId : {}", claimRequest.getRequestId());
logger.debug(" responseId : {}", claimRequest.getResponseId());
logger.debug(" request date : {}", claimRequest.getRequestDate());
logger.debug(" nonce : {}\n", claimRequest.getNonce());
// Extract the Did and public key id of the Owner
String did = claimRequest.getDid();
String keyId = claimRequest.getKeyId();
// Query the public key from the blockchain
Document ownerDocument = didService.readDocument(did);
PublicKeyProperty publicKeyProperty = ownerDocument.getPublicKeyProperty(keyId);
// Check if the public key of the owner has been revoked. If it has, return error
boolean isRevoked = publicKeyProperty.isRevoked();
PublicKey publicKey = publicKeyProperty.getPublicKey();
// Check signature
Jwt.VerifyResult verifyResult = claimRequest.verify(publicKey);
verifyResult.isSuccess();
verifyResult.getFailMessage();
After an issuer successfully validates a request from an owner, the issuer can issue a set of credential
to the owner, which has the owner's DID and recently validated information embedded inside of it. Also, the issuer's DID must already be registered on the blockchain before this step can proceed. (See: DID Document Registration)
The credential
can be only be created after instantiating a KeyProvider
that contains the issuer's DID information.
// DID of the issuer
String did = "did:icon:01:...1";
// Information about the public key used in the DID document
String keyId = "EmailIssuer";
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K;
// private key corresponding to the public key
String privateKey = "..."; // base64
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
DidKeyHolder issuerKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
ClaimRequest claimRequest = ..; // owner로부터 받은 request object
// Create Credential instance
Credential credential = new Credential.Builder()
.didKeyHolder(issuerKeyHolder)
.nonce(claimRequest.getNonce()) // (optional)
.build();
Add the owner's DID and its claims to the credential
and issue a JWT.
// Configure owner's DID and credentials
String ownerDid = "did:icon:01:...1";
credential.setTargetDid(ownerDid);
credential.setVersion(version);
credential.addClaim("email", "abc@icon.foundation");
// Set expiration date
Date issued = new Date();
// Default settings
long duration = credential.getDuration() * 1000L; // to milliseconds (for Date class)
Date expiration = new Date(issued.getTime() + duration);
// Issue the signed credential token
String token = issuerKeyHolder.sign(credential.buildJwt(issued, expiration));
The token is as follows. (See: JWT debugger)
// header
{
"alg": "ES256K",
"kid": "did:icon:01:849625146b531abdff5c0f87acd8d1c20f927c8f7ecd96c3#EmailIssuer"
}
// payload
{
"version": "1.0",
"claim": {
"email": "abc@icon.foundation"
},
"exp": 1553667802,
"iat": 1553581402,
"iss": "did:icon:01:12802a771fa8f74d716366c170632010850587d56788cd76",
"sub": "did:icon:01:5ea58f6949183cb9ba996f512f3ab56c2d88f0e459dd3f33",
"type": [
"CREDENTIAL",
"email"
]
}
// signature
A presentation
including credential
is signed and produced. The credential
can then be provided to a verifier
.
A verifier
can request a presentation
by following the steps depicted below.
// DID of an owner the verifier is sending the request to
String ownerDid = "did:icon:01:...1";
Date requestDate = new Date();
// Credentials that the verifier wishes to verify
List<String> claimTypes = Arrays.asList("email");
// If the verifier does not have a DID
ClaimRequest request = new ClaimRequest.Builder(ClaimRequest.Type.PRESENTATION)
.algorithm(AlgorithmProvider.Type.NONE)
.responseId(ownerDid)
.requestDate(requestDate)
.requestClaimTypes(claimTypes)
.version(version)
.build();
// Unsigned JWT
String unsigendJwt = request.compact();
// If the verifier has an existing DID
String keyId = "verifier";
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256K;
// Private key corresponding to the public key
String privateKey = "..."; // base64
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
DidKeyHolder verifierKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
// Create random nonce that will be used when sending request
String nonce = Hex.toHexString(AlgorithmProvider.secureRandom().generateSeed(4));
ClaimRequest request = new ClaimRequest.Builder(ClaimRequest.Type.PRESENTATION)
.didKeyHolder(verifierKeyHolder)
.responseId(ownerDid)
.requestDate(requestDate)
.requestClaimTypes(claimTypes)
.nonce(nonce)
.version(version)
.build();
// Signed JWT
String sigendJwt = verifierKeyHolder.sign(request.getJwt());
The resulting token is as follows. (See: JWT debugger)
// header
{
"alg": "none"
}
// payload
{
"version": "1.0",
"iat": 1553583104,
"iss": "did:icon:01:12802a771fa8f74d716366c170632010850587d56788cd76",
"aud": "did:icon:01:5ea58f6949183cb9ba996f512f3ab56c2d88f0e459dd3f33",
"type": [
"REQ_PRESENTATION",
"email"
]
}
When a verifier
requests certain credentials
from an owner
, the owner
creates a presentation
corresponding to the requested credentials
and provides it to the verifier
.
// DID of owner
String did = "did:icon:01:...1";
// Info of public key registered on DID document
String keyId = "owner";
AlgorithmProvider.Type type = AlgorithmProvider.Type.ES256;
// Private key corresponding to the public key
String privateKey = "..."; // base64
Algorithm algorithm = AlgorithmProvider.create(type);
PrivateKey pk = algorithm.byteToPrivateKey(EncodeType.BASE64.decode(privateKey));
DidKeyHolder ownerKeyHolder = new DidKeyHolder.Builder()
.did(did)
.keyId(authKeyId)
.type(type)
.privateKey(pk)
.build();
// Request object that the verifier has sent
ClaimRequest claimRequest = ..;
// Create presentation instance
Presentation presentation = new Presentation.Builder()
.didKeyHolder(ownerKeyHolder)
.nonce(request.getNonce())
.version(version)
.build();
Add the owner's claims and issue a JWT
// Credential token the the issuer has sent
String credential = "eyJ0eXA...";
// Add credential
presentation.addCredential(credential);
// The default expiration time of a presentation token is 5 minutes (in order to prevent malicious requests)
// Issue a signed presentation token
String token = ownerKeyHolder.sign(presentation.buildJwt());
The JWT is as follows. (See: JWT debugger)
// header
{
"typ": "JWT",
"alg": "ES256K",
"kid": "did:icon:01:e96721825d09683be1438800e976ab498a0cf4fafca29316#owner"
}
// payload
{
"version": "1.0",
"credential": [
"eyJhb...E"
],
"exp": 1553586450,
"iat": 1553586150,
"iss": "did:icon:01:5ea58f6949183cb9ba996f512f3ab56c2d88f0e459dd3f33",
"type": [
"PRESENTATION",
"email"
]
}
// signature
A verifier can verify the presentation it has received from a owner using the following methods. (See: Register presentation)
The verifier
must verify both the presentation token that the owner
has issued and the credentials that an issuer
has issued that is embedded inside the owner
's token.
The public keys of the owner and issuer are required in order to verify the token. These public keys can be viewed by querying the blockchain. (See: DidService instantiation);
The below steps should be followed to verify the token that an owner
has sent.
// JWT that the owner has sent
String token = "eyJ0eX...";
// Create presentation instance using the token
Presentation presentation = Presentation.valueOf(token);
// Extract DID and public key id of the owner
String ownerDid = presentation.getDid();
String ownerKeyId = presentation.getKeyId();
// Query the public key from the blockchain
Document ownerDocument = didService.readDocument(ownerDid);
// Confirm the public key of the owner has been revoked
PublicKeyProperty publicKeyProperty = ownerDocument.getPublicKeyProperty(ownerKeyId);
// If public key of the owner has been revoked, return error (revoke: true)
boolean isRevoked = publicKeyProperty.isRevoked();
PublicKey publicKey = publicKeyProperty.getPublicKey();
// Verify results
Jwt.VerifyResult verifyResult = Jwt.decode(token).verify(publicKey);
verifyResult.isSuccess();
verifyResult.getFailMessage();
Verify the credential
that is embedded inside the presentation
token
// DID of the owner that sent the presentation token
String ownerDid = "...owner did...";
// Extract credentials
List<String> claims = credential.getClaims();
for (String credentialJwt : claims) {
// Create Credential instance using the token
Credential credential = Credential.valueOf(credentialJwt);
// Extract DID and public key id of the issuer
String issuerDid = credential.getDid();
String issuerKeyId = credential.getKeyId();
// Query the public key of the issuer from the blockchain
Document issuerDocument = didService.readDocument(issuerDid);
// Check if the public key of the owner has been revoked
PublicKeyProperty publicKeyProperty = issuerDocument.getPublicKeyProperty(issuerKeyId);
// If public key has been revoked, return error (revoke: true)
boolean isRevoked = publicKeyProperty.isRevoked();
PublicKey publicKey = publicKeyProperty.getPublicKey();
Jwt.VerifyResult verifyResult = Jwt.decode(credentialJwt).verify(publicKey);
verifyResult.isSuccess();
verifyResult.getFailMessage();
// Check if owner's DID equals the subject that the issuer has verified
boolean checkTarget = ownerDid.equals(credential.getSubject());
}
After validating the owner and issuer, the verified claims can be viewed by extracting them from the credential
object.
// Extract credential type
String type = credential.getType();
// Check claims
Map<String, Object> claim = credential.getClaim();
0.8.6 (beta)
Download the latest JAR
Add dependencies
implementation "foundation.icon:icon-sdk:0.9.11"
implementation "com.google.code.gson:gson:2.8.0"
implementation "org.bouncycastle:bcprov-jdk15on:1.60"