This article defines the specifications of Weelink DID-Auth-Protocol in detail for users’ reference.
Weelink DID is a new blockchain-based authentication method that follows all the requirements of W3C. Based on Weelink Wallet, our method provides a series of APIs and services for a fast and secure authentication process.
The rapid development of Internet has provided unparalled convenience to the public, yet it is also inevitably bringing some potential problems. One of them is the leaking of private information that comes with traditional authentication systems. Taking advantage of blockchain’s decentralization and security characteristics, we thus propose our secure authentication method.
Mainly used for declaring specifications for DID string.
Choose one directly Pick a random secret key or based on those that the entity has (“entity” here refers to Weelink Wallet).
Create with appPk
(use bip44 to calculate DID)
Currently the creation of wallet accounts in Weelink Wallet is based on bip44.
appDid
with sh3s1
and s2
m/44'/WLK'/S1'/S2'/address_index
Choose RoleType
,KeyType
and HashType
. DID is the first two bytes of of DID string in binary format. DID Type consists of the following three parts.
RoleType
:first six bits
To be expanded in the future.
- account = 0
- node = 1
- device = 2
- application = 3
- smart_contract = 4
- bot = 5
- asset = 6
- stake = 7
- validator = 8
- group = 9
- any = 63
KeyType
:middle five bits
Used for specifying the algorithm that converts secret keys to public keys.
- ED25519 = 0
- SECP256K1 = 1
Hash
:last five bits
Used for calculating public key hash function.
- keccak = 0
- sha3 = 1
- keccak_384 = 2
- sha3_384 = 3
- keccak_512 = 4
- sha3_512 = 5
As an example, DID Type 0x0C01
can be decoded like the following
+-------------+-----------+------------+
| 000011 | 00000 | 00001 |
+-------------+-----------+------------+
| application | ed25519 | sha3 |
+-------------+-----------+------------+
Use the method corresponding to KeyType
to convert secret key to public key.
For instance:E4852B7091317E3622068E62A5127D1FB0D4AE2FC50213295E10652D2F0ABFC7
EC8E681514753FE5955D3E8B57DAEC9D123E3DB146BDDFC3787163F77F057C27
EC8E681514753FE5955D3E8B57DAEC9D123E3DB1
0x0C01
before the hash of Step 50C01EC8E681514753FE5955D3E8B57DAEC9D123E3DB1
42CD815145538F8003586C880AF94418341F9C4B8FA0394876553F8A952C7D03
42CD8151
Put the 4 bytes from Step 8 to the end of hash from Step 6 to get the DID String in binary format.
0C01EC8E681514753FE5955D3E8B57DAEC9D123E3DB142CD8151
Encode the binary file with Base58 method and add ch
before the result to get DID String
chNKtCNqYWLYWYW3gWRA1vnRykfCBZYHZvzKr
did:wlk:chNKtCNqYWLYWYW3gWRA1vnRykfCBZYHZvzKr
DeclareDID is done by sending a transaction to the blockchain. Here’s a sample transaction:
{
"hash": "36BBCA0115A52C0F43C42E84CAE368481A0F32B218380721E3DD2B0456D1D294",
"tx": {
"from": "z1RMrcjJVwuohBoqAsPaVvuDajQi1fDo8Qx",
"itx": {
"__typename": "DeclareTx",
"data": null,
"pk": "IWNMqz5IdsqxO0x9iqdlSfMvPkchVc3un8mmLXT_GcU",
"type": {
"address": "BASE58",
"hash": "SHA3",
"pk": "ED25519",
"role": "ROLE_ACCOUNT"
}
},
"nonce": 1,
"signature": "E_BkPhw-WUpkTk5nn_WF4z-8huOBqjl-3vQ122TYCDQiahFlklVJT3I7YUwr8d-pi_mqMM0JKWB06ayJh3gODQ",
"signatures": []
}
}
This is essentially putting the DID document on the blockchain.
Reading DID is as simple as sending a GRPC request to WLK network. The following is a description of the request’s structure. address
field is the DID to query. The omission of keys
field will return the entire account status. height
field can be used to locate DID documents of previous versions. If omitted, the latest document will be returned.
message RequestGetAccountState {
string address = 1;
repeated string keys = 2;
uint64 height = 3;
}
Response contains the DID document associated with this DID.
Sending a transaction like this can update an associated DID document:
{
"hash": "36BBCA0115A52C0F43C42E84CAE368481A0F32B218380721E3DD2B0456D1D294",
"tx": {
"from": "z1RMrcjJVwuohBoqAsPaVvuDajQi1fDo8Qx",
"itx": {
"__typename": "UpdateTx",
"data": "The new data to replace the existing one.",
"pk": "IWNMqz5IdsqxO0x9iqdlSfMvPkchVc3un8mmLXT_GcU",
},
"nonce": 1,
"signature": "E_BkPhw-WUpkTk5nn_WF4z-8huOBqjl-3vQ122TYCDQiahFlklVJT3I7YUwr8d-pi_mqMM0JKWB06ayJh3gODQ",
"signatures": []
}
}
Please note that old versions of DID documents are still stored on the chain due to chain’s data structures. DID documents won’t be updated with this operation. Rather, a new version will be put over existing ones.
Revoking DID can be done by sending RevokeTx transaction to mark the DID document as revoked. Although the DID document will be considered revoked from the block containing the accepted transaction, the document is still stored on the chain instead of being erased.
{
"hash": "36BBCA0115A52C0F43C42E84CAE368481A0F32B218380721E3DD2B0456D1D294",
"tx": {
"from": "z1RMrcjJVwuohBoqAsPaVvuDajQi1fDo8Qx",
"itx": {
"__typename": "RevokeTx",
"pk": "IWNMqz5IdsqxO0x9iqdlSfMvPkchVc3un8mmLXT_GcU",
},
"nonce": 1,
"signature": "E_BkPhw-WUpkTk5nn_WF4z-8huOBqjl-3vQ122TYCDQiahFlklVJT3I7YUwr8d-pi_mqMM0JKWB06ayJh3gODQ",
"signatures": []
}
}
When the DID has been successfully created, it’s time to create a corresponding DID ocument, the basic format of which is as follows:
{
"@context": "https://w3id.org/future-method/v1",
"id": "did:example:123456789abcdefghi",
"publicKey": [],
"authentication": [],
"service": []
}
- DID and the public key corresponding to its signature should be stored when a DID is created for the first time.
The DID Document should be published to the chain after creation.
Users sign DID with their private keys and DID Resolvers use public keys of the DID document for verification.
The core of updating a DID document is updating server endpoints.
server endpoint data
with private keys. Server endpoint data
is comprised of the following structures:
```The following description is mainly for the procedure associated with DID auth. Using other server endpoints is similar.
Three processes are involved in using DID for the entire authentication protocol:Pre-knowledge
,Request DID Authentication
and Response DID Authentication
. Each of them will be detailed in this section.
Pre-knowledge
refers to the process of obtaining application information prior to starting the real authentication. The wallet needs to know the application’s DID, public key and service endpoints beforehand. QR code or deep link
can be used to provide this information.
deep link
https://wallet.io/i?appPk=zBdZEnbDJTijVVCx4Nx68bzDPPMFwVizSRorvzSS3SGG2&appDid=did:wlk:chNK7PeUtemp5oAhJ4zNmGJ8rUoFnB1CtKfoU&action=requestAuth&url=https://example-application.io/auth/
linkPath
: linkPath
is located at the very beginning of a link. In this example, it is https://wallet.io/i
, used for locating Wallet. This part is configurable: SDK allows developers to register their own domains for applications. In other words, it is generated through Weelink.
apppk
:This is a Base59 encoded public key passed from an application to Wallet.appid
:It refers to the app’s id in Weelink.appDid
:The application’s DID.action
:It is used for telling Wallet what to do next. The action here should be requestAuth
and Wallet will visit the url via GET
method.url
:Wallet will use this x-www-form-urlencoded URL to start the Request DID Authentication process.Upon receiving the above information, Wallet will begin the process of requesting DID authentication in order to acquire the app-requested verifiable claims.
GET https://example-application.io/auth?userDid=encrypted_userDid
appPk
:application’s Base58-encoded public keyauthInfo
:signed object in JWT formatAn example:
{ "appPk": "zBdZEnbDJTijVVCx4Nx68bzDPPMFwVizSRorvzSS3SGG2", "authInfo": "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIn0.eyJleHAiOjE1NDg4MDM0MjIsImlhdCI6MTU0ODcwMzQyMiwiaXNzIjoiZGlkOmFidDp6Tkt0Q05xWVdMWVdZVzNnV1JBMXZuUnlrZkNCWllIWnZ6S3IiLCJuYmYiOjE1NDg3MDM0MjIsInJlcXVlc3RlZENsYWltcyI6eyJkb2N1bWVudHMiOlt7Imhhc2giOiJUaGUgaGFzaCBvZiB0aGUgZG9jdW1lbnQncyBjb250ZW50IiwidXJpIjoiaHR0cHM6Ly9kb2N1bWVudC0xLmlvIn0seyJoYXNoIjoiVGhlIGhhc2ggb2YgdGhlIGRvY3VtZW50J3MgY29udGVudCIsInVyaSI6ImlwZnM6Ly9kb2N1bWVudC0yIn1dLCJwcm9maWxlIjpbImZ1bGxOYW1lIiwicGhvbmUiLCJzaGlwcGluZ0FkZHJlc3MiXSwicHJvb2ZPZkhvbGRpbmciOlt7InRva2VuIjoidG9rZW4gbmFtZSAxIiwidmFsdWUiOjE4MDAwMDB9LHsidG9rZW4iOiJ0b2tlbiBuYW1lIDIiLCJ2YWx1ZSI6MTAwMDAwMH1dfSwicmVzcG9uc2VBdXRoVXJpIjoiaHR0cHM6Ly9leGFtcGxlLWFwcGxpY2F0aW9uL3Jlc3BvbnNlLWF1dGgifQ.RasZv6ydSxOBj3H726P8THeo4K4IAd7wapqrdE4hrOVRONByAHYK1kr7uAXASc_-Mw9ShD3IcqAuwnLiEkvHCQ" }
The header and body of authInfo
shown above can be decoded into:
{
"alg": "Ed25519",
"typ": "JWT"
}
{
"iss": "did:wlk:zNKtCNqYWLYWYW3gWRA1vnRykfCBZYHZvzKr",
"iat": 1548703422,
"nbf": 1548703422,
"exp": 1548803422,
"appInfo": {
"name": "The name of the application",
"description": "The description of the application.",
"logo": "https://example-application/logo"
},
"action": "responseAuth",
"url": "https://example-application/auth",
"requestedClaims": [
{
"type": "profile",
"meta": {
"description": "Please fill in basic information."
},
"items": ["fullName", "mobilePhone", "mailingAddress"]
},
{
"type": "agreement",
"meta": {
"description": "The user data usage agreement."
},
"uri": "https://document-1.io",
"hash": {
"method": "sha256",
"digest": "The hash result of the document's content"
}
},
{
"type": "agreement",
"meta": {
"description": "The service agreement"
},
"uri": "ipfs://document-2",
"hash": {
"method": "sha3",
"digest": "The hash result of the document's content"
}
}
]
}
iss
: application’s DID generated from appPk.iat
,nbf
and exp
: they follow the JWT standard.appInfo
: basic information of application.url
: a required field that Wallet uses when answering DID authentication.action
: Wallet’s action in the next step. Here it should be responseAuth
and Wallet will access the url
with POST
method.requestedClaims
: an optional field. It can ask users to identify themselves by returning this field if users are unknown to the application. Details of this is illustrated in a different section. The application can omit this field if needed. Upon receiving the response, the following authentication should be done by Wallet:
iat
is after sending the request.exp
.appPk
and appPk
matches appDid
in iss
fieldtrustLevel
, from a registery blockchain. WellLink provides WLK chain as a registery chain.trustLevel
when displaying requested claim to user. Wallet should display a high risk mark on the entire page for applications whose appDid
cannot be found on registery blockchain. High risk marks should also be displayed on application-requested verifiable claims whose required trustLevel
is higher than appDid
.This is the last process of the entire workflow. Depending on whether the application is asking for verifiable claims, Wallet will either prompt users to fill in those claims and go to responseAuth endpoint or to go to the endpoint directly.
user_did
to sign valid payloads with corresponding private keys, then send it back to the URL obtained from the DID authentication process in the following format.{
"userPk": "",
"userInfo": ""
}
userInfo
above can be decoded into:
{
"alg": "Ed25519",
"typ": "JWT"
}
{
"iss": "userDid",
"iat": "1548713422",
"nbf": "1548713422",
"exp": "1548813422",
"requestedClaims": [
{
"type": "profile",
"fullName": "Alice Bean",
"mobilePhone": "123456789",
"mailingAddress": {
"addressLine1": "456 123th AVE",
"addressLine2": "Apt 106",
"city": "Redmond",
"state": "WA",
"postalCode": "98052",
"country": "USA"
}
},
{
"type": "agreement",
"uri": "https://document-1.io",
"hash": {
"method": "sha256",
"digest": "The hash result of the document's content"
},
"agreed": true,
"sig": "user's signature against the doc hash plus AGREED."
},
{
"type": "agreement",
"uri": "ipfs://document-2",
"hash": {
"method": "sha3",
"digest": "The hash result of the document's content"
},
"agreed": false
}
]
}
{
"appPk": "E4852B7091317E3622068E62A5127D1FB0D4AE2FC50213295E10652D2F0ABFC7",
"jwt": "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIn0.eyJleHAiOiIxNTQ4ODk4ODM5IiwiaWF0IjoiMTU0ODg5NzAzOSIsImlzcyI6ImRpZDphYnQ6ek5LdENOcVlXTFlXWVczZ1dSQTF2blJ5a2ZDQlpZSFp2ektyIiwibmJmIjoiMTU0ODg5NzAzOSJ9.OtJDYOLEF_AtBD6qikE-zg-qnzrJnq1OQ2A9dgiLcWxWNZJjEQdUgei-ZfAB3QJ7zPFLxf-m33TS34WJ6cpbCg"
}
{
"alg": "Ed25519",
"typ": "JWT"
}
{
"exp": "1548898839",
"iat": "1548897039",
"iss": "did:wlk:zNKtCNqYWLYWYW3gWRA1vnRykfCBZYHZvzKr",
"nbf": "1548897039"
}
Authentication
field.The ways of creating, registering and managing DIDs in DID methods are designed to provide enhanced privacy, improved anonymity and reduced correlation risk.
Keep personally-identifiable information (PII) off-ledger. Chains store signatures, not PII. A claim verifier asks the peer to be verified for the original data.
DID Correlation Risks and Pseudonymous DIDs. Shown in the first step of Request DID Authentication, generating application-specific DID enforces pseudonymous DID and privacy across chains. A user might have multiple extended DIDs under one master DID and use those extended DIDs on different chains. The master DID would never, through any means, be exposed.
DID Document Correlation Risks are lowered by isolating DID documents corresponding to extended DIDs of the same master DID.
The underlying blockchain is also designed to tackle the following security risks.
Our blockchain-based implementation has covered every single requirement listed in W3C DID specification.
Verifiable claims is a list of claim items. Every claim item must have a type
field and can contain an optional meta
field.
Until now, there are three kinds of verifiable claims:
profile
:This can contain multiple universally-known predefined claim items, such as firstName
and birthday
.agreement
:A peer may ask users to sign agreements.proofOfHolding
: A peer may ask users to prove that they have either a certain amount of tokens or a certificate issued by a third party.meta
is an optional field which may contain, but not limited to, the following field:
description
: This is used for describing claim. Wallet can shown users this field.Profile is the easiest verifiable claim for collecting users’ basic information. It should contain the following fields:
type
:fixed to “profile”.meta
:an optional field.items
:a list of predefined profile items.When a peer needs profile claims, it should include a list of profile items in the response.
{
"requestedClaims": [
{
"type": "profile",
"meta": {
"description": "Please provide the basic information.",
},
"items": ["fullName", "mobilePhone", "mailingAddress"]
}
]
}
Upon receiving this response, Wallet should prompt the user to fill in data. Afterwards, Wallet should return the claims in the following format.
{
"requestedClaims": [
{
"type": "profile",
"meta": {
"description": "Please provide the basic information",
},
"fullName": "Alice Bean",
"mobilePhone": "123456789",
"mailingAddress": {
"addressLine1": "456 123th AVE",
"addressLine2": "Apt 106",
"city": "Redmond",
"state": "WA",
"postalCode": "98052",
"country": "USA"
}
}
]
}
Agreement is another common type of claim. It represents an agreement a peer asks users to sign. A claim of agreeement
type should contain the following fields:
type
:fixed to “agreement”.meta
:optional field.uri
:points to the content of agreement.hash
:an object where the method
subfield specifies the algorithm (sha3, sha256, etc) used and the digest
subfield is the hash result.agreed
:a boolean value added by Wallet to represent whether the user accepts the agreement.sig
:the DSA signature of hash.When peer wants user-signed agreements, it should add a list of claim item of agreement type in the response. Every claim item should have a meta
that contains the agreement’s URI and a summary of agreement content.
{
"requestedClaims": [
{
"type": "agreement",
"meta": {
"description": "The user data usage agreement.",
},
"uri": "https://document-1.io",
"hash": {
"method": "sha256",
"digest": "The hash result of the document's content"
}
},
{
"type": "agreement",
"meta": {
"description": "The service agreement",
},
"uri": "ipfs://document-2",
"hash": {
"method": "sha3",
"digest": "The hash result of the document's content"
}
}
]
}
Wallet will prompt the user to sign the agreement after getting the response. Afterwards, Wallet shall submit a list of signed claim items to peer. If agreed by the user, Wallet shall add AGREED
and user-signed sig
field to the response
field. If declined by user, Wallet will only need to add DECLINED
to response
field. No signature is needed under this circumstance.
{
"requestedClaims": [
{
"type": "agreement",
"uri": "https://document-1.io",
"hash": {
"method": "sha256",
"digest": "The hash result of the document's content"
},
"agreed": true,
"sig": "user's signature against the doc digest plus AGREED."
},
{
"type": "agreement",
"uri": "ipfs://document-2",
"hash": {
"method": "sha3",
"digest": "The hash result of the document's content"
},
"agreed": false
}
]
}
Registry blockchain is where application DID registration should take place. As a decentralized agency, it tells Wallet regarding whether the application asking for can be trusted. A registry blockchain should provide at least the following information about the application: trustLevel
.
trustLevel
is a number that provides insights into an application’s relative reliability. It is the registry blockchain’s responsibility to maintain the trust level of an application. If an application’s done something despicable, it will be punished with a decreasing trustLevel
resulting from voting.
Calculating and declaring DID.
Wallet-called functions for getting appDid’s metadata.
Helper functions for constructing the encoded challenge to be signed.
Helper functions for verifying the signature and DID of a challenge.
verify_challenge(challenge, pk)
verify_did(pk, did)