Skip to content

Commit

Permalink
Add DID @context and Extend authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
Youngjoon Lee committed Sep 10, 2020
1 parent 78149f3 commit ac147c3
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 23 deletions.
9 changes: 7 additions & 2 deletions docs/did.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ It must be included in the subsequent transaction (update/deactivate) for preven

Only the DID owner can replace the DID Document using the following transaction.

This example is for adding a new public key to the DID document.
This example is for adding a new public key to the `publicKey` and adding a dedicated public key to the `authentication`.
```json
{
"type": "did/MsgUpdateDID",
Expand All @@ -210,7 +210,12 @@ This example is for adding a new public key to the DID document.
}
],
"authentication": [
"did:panacea:mainnet:G3UzSnRRsyApppuHVuaff#key1"
"did:panacea:mainnet:G3UzSnRRsyApppuHVuaff#key1",
{
"id": "did:panacea:mainnet:G3UzSnRRsyApppuHVuaff#key3",
"type": "Secp256k1VerificationKey2018",
"publicKeyBase58": "yE1om4991ANiFrwZJ3Ev5YYX9KiPKgaHmGsi2Bjcxuwij"
}
]
},
"signature": "xtsQH3D5naHe9IXmhCnohlChwHiD0dx9PI4aPkaJPGoEznYMHmg0aBerg85ai7T2WNxxlc39uFzAxKbI4sbJCA==",
Expand Down
145 changes: 133 additions & 12 deletions x/did/types/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,18 @@ func networkIDRegex() string {
}

type DIDDocument struct {
Contexts Contexts `json:"@context"`
ID DID `json:"id"`
PubKeys []PubKey `json:"publicKey"`
Authentications []Authentication `json:"authentication"`
}

func NewDIDDocument(id DID, pubKey PubKey) DIDDocument {
return DIDDocument{
Contexts: Contexts{ContextDIDV1},
ID: id,
PubKeys: []PubKey{pubKey},
Authentications: []Authentication{Authentication(pubKey.ID)},
Authentications: []Authentication{newAuthentication(pubKey.ID)},
}
}

Expand All @@ -95,6 +97,10 @@ func (doc DIDDocument) Valid() bool {
return false
}

if doc.Contexts == nil || !doc.Contexts.Valid() {
return false
}

for _, pubKey := range doc.PubKeys {
if !pubKey.Valid(doc.ID) {
return false
Expand All @@ -105,8 +111,10 @@ func (doc DIDDocument) Valid() bool {
if !auth.Valid(doc.ID) {
return false
}
if _, ok := doc.PubKeyByID(KeyID(auth)); !ok {
return false
if !auth.hasDedicatedPubKey() {
if _, ok := doc.PubKeyByID(auth.KeyID); !ok {
return false
}
}
}

Expand All @@ -129,14 +137,76 @@ func (doc DIDDocument) GetSignBytes() []byte {

// PubKeyByID finds a PubKey by ID.
// If the corresponding PubKey doesn't exist, it returns a false.
func (doc DIDDocument) PubKeyByID(id KeyID) (*PubKey, bool) {
for i := 0; i < len(doc.PubKeys); i++ {
pubKey := &doc.PubKeys[i]
if pubKey.ID == id {
return pubKey, true
func (doc DIDDocument) PubKeyByID(id KeyID) (PubKey, bool) {
//TODO: Sadly, Amino codec doesn't accept maps. Find the way to make this efficient.
for _, auth := range doc.Authentications {
if auth.KeyID == id {
for _, pubKey := range doc.PubKeys {
if pubKey.ID == id {
return pubKey, true
}
}
return PubKey{}, false
}
}
return nil, false
return PubKey{}, false
}

type Contexts []Context

func (ctxs Contexts) Valid() bool {
if ctxs == nil || len(ctxs) == 0 || ctxs[0] != ContextDIDV1 { // the 1st one must be ContextDIDV1
return false
}

set := make(map[Context]struct{}, len(ctxs))
for _, ctx := range ctxs {
_, dup := set[ctx] // check the duplication
if dup || !ctx.Valid() {
return false
}
set[ctx] = struct{}{}
}
return true
}

func (ctxs Contexts) MarshalJSON() ([]byte, error) {
if len(ctxs) == 1 { // if only one, treat it as a single string
return didCodec.MarshalJSON(ctxs[0])
}
return didCodec.MarshalJSON([]Context(ctxs)) // if not, as a list
}

func (ctxs *Contexts) UnmarshalJSON(bz []byte) error {
var single Context
err := didCodec.UnmarshalJSON(bz, &single)
if err == nil {
*ctxs = Contexts{single}
return nil
}

var multiple []Context
if err := didCodec.UnmarshalJSON(bz, &multiple); err != nil {
return err
}
*ctxs = multiple
return nil
}

type Context string

const (
ContextDIDV1 Context = "https://www.w3.org/ns/did/v1"
ContextSecurityV1 Context = "https://w3id.org/security/v1"
)

func (ctx Context) Valid() bool {
switch ctx {
case ContextDIDV1, ContextSecurityV1:
return true
default:
return false
}
}

type KeyID string
Expand Down Expand Up @@ -219,11 +289,62 @@ func (pk PubKey) Valid(did DID) bool {
return matched
}

// TODO: to be extended
type Authentication KeyID
type Authentication struct {
KeyID KeyID
// DedicatedPubKey is not nil if it is only authorized for authentication
// https://www.w3.org/TR/did-core/#example-18-authentication-property-containing-three-verification-methods
DedicatedPubKey *PubKey
}

func newAuthentication(keyID KeyID) Authentication {
return Authentication{KeyID: keyID, DedicatedPubKey: nil}
}

func newAuthenticationDedicated(pubKey PubKey) Authentication {
return Authentication{KeyID: pubKey.ID, DedicatedPubKey: &pubKey}
}

func (a Authentication) hasDedicatedPubKey() bool {
return a.DedicatedPubKey != nil
}

func (a Authentication) Valid(did DID) bool {
return KeyID(a).Valid(did)
if !a.KeyID.Valid(did) {
return false
}
if a.DedicatedPubKey != nil {
if !a.DedicatedPubKey.Valid(did) || a.DedicatedPubKey.ID != a.KeyID {
return false
}
}
return true
}

func (a Authentication) MarshalJSON() ([]byte, error) {
// if dedicated
if a.DedicatedPubKey != nil {
return didCodec.MarshalJSON(a.DedicatedPubKey)
}
// if not dedicated
return didCodec.MarshalJSON(a.KeyID)
}

func (a *Authentication) UnmarshalJSON(bz []byte) error {
// if not dedicated
var keyID KeyID
err := didCodec.UnmarshalJSON(bz, &keyID)
if err == nil {
*a = newAuthentication(keyID)
return nil
}

// if dedicated
var pubKey PubKey
if err := didCodec.UnmarshalJSON(bz, &pubKey); err != nil {
return err
}
*a = newAuthenticationDedicated(pubKey)
return nil
}

// DIDDocumentWithSeq is for storing a Sequence along with a DIDDocument.
Expand Down
111 changes: 104 additions & 7 deletions x/did/types/did_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,64 @@ func TestNewDIDDocument(t *testing.T) {
require.Equal(t, 1, len(doc.PubKeys))
require.Equal(t, pubKey, doc.PubKeys[0])
require.Equal(t, 1, len(doc.Authentications))
require.EqualValues(t, keyID, doc.Authentications[0])
require.EqualValues(t, keyID, doc.Authentications[0].KeyID)
}

func TestDIDDocument_Empty(t *testing.T) {
require.False(t, getValidDIDDocument().Empty())
require.True(t, DIDDocument{}.Empty())
}

pubKeyFound, ok := doc.PubKeyByID(keyID)
func TestDIDDocument_PubKeyByID(t *testing.T) {
did := DID("did:panacea:testnet:KS5zGZt66Me8MCctZBYrP")
keyID := NewKeyID(did, "key1")
pubKey := NewPubKey(keyID, ES256K, secp256k1.GenPrivKey().PubKey())
doc := NewDIDDocument(did, pubKey)

found, ok := doc.PubKeyByID(keyID)
require.True(t, ok)
require.Equal(t, pubKey, *pubKeyFound)
require.Equal(t, pubKey, found)

_, ok = doc.PubKeyByID(NewKeyID(did, "key2"))
require.False(t, ok)

_, ok = doc.PubKeyByID("invalid_key_id")
doc.Authentications = []Authentication{} // clear authentications
_, ok = doc.PubKeyByID(keyID)
require.False(t, ok)
}

func TestDIDDocument_Empty(t *testing.T) {
require.False(t, getValidDIDDocument().Empty())
require.True(t, DIDDocument{}.Empty())
func TestContexts_Valid(t *testing.T) {
require.False(t, Contexts{}.Valid())
require.True(t, Contexts{ContextDIDV1}.Valid())
require.True(t, Contexts{ContextDIDV1, ContextSecurityV1}.Valid())
require.False(t, Contexts{ContextSecurityV1, ContextDIDV1}.Valid())
require.False(t, Contexts{ContextDIDV1, "https://something.com"}.Valid())
require.False(t, Contexts{ContextDIDV1, ContextDIDV1}.Valid())

var ctxs Contexts = nil
require.False(t, ctxs.Valid())
}

func TestContexts_MarshalJSON(t *testing.T) {
bz, err := didCodec.MarshalJSON(Contexts{ContextDIDV1})
require.NoError(t, err)
require.Equal(t, fmt.Sprintf(`"%v"`, ContextDIDV1), string(bz))

bz, err = didCodec.MarshalJSON(Contexts{ContextDIDV1, ContextSecurityV1})
require.NoError(t, err)
require.Equal(t, fmt.Sprintf(`["%v","%v"]`, ContextDIDV1, ContextSecurityV1), string(bz))
}

func TestContexts_UnmarshalJSON(t *testing.T) {
var ctxs Contexts

bz := []byte(fmt.Sprintf(`["%v","%v"]`, ContextDIDV1, ContextSecurityV1))
require.NoError(t, didCodec.UnmarshalJSON(bz, &ctxs))
require.Equal(t, Contexts{ContextDIDV1, ContextSecurityV1}, ctxs)

bz = []byte(fmt.Sprintf(`"%v"`, ContextDIDV1))
require.NoError(t, didCodec.UnmarshalJSON(bz, &ctxs))
require.Equal(t, Contexts{ContextDIDV1}, ctxs)
}

func TestNewKeyID(t *testing.T) {
Expand Down Expand Up @@ -127,6 +172,58 @@ func TestNewPubKey(t *testing.T) {
require.Equal(t, expected[:], base58.Decode(pub.KeyBase58))
}

func TestAuthentication_Valid(t *testing.T) {
did := DID("did:panacea:testnet:KS5zGZt66Me8MCctZBYrP")
keyID := NewKeyID(did, "key1")
pubKey := NewPubKey(keyID, ES256K, secp256k1.GenPrivKey().PubKey())

auth := Authentication{KeyID: keyID, DedicatedPubKey: nil}
require.True(t, auth.Valid(did))
auth = Authentication{KeyID: keyID, DedicatedPubKey: &pubKey}
require.True(t, auth.Valid(did))

auth = Authentication{KeyID: "invalid", DedicatedPubKey: nil}
require.False(t, auth.Valid(did))
auth = Authentication{KeyID: keyID, DedicatedPubKey: &PubKey{ID: "invalid"}}
require.False(t, auth.Valid(did))
auth = Authentication{KeyID: NewKeyID(did, "key2"), DedicatedPubKey: &pubKey}
require.False(t, auth.Valid(did))
}

func TestAuthentication_MarshalJSON(t *testing.T) {
did := DID("did:panacea:testnet:KS5zGZt66Me8MCctZBYrP")
keyID := NewKeyID(did, "key1")
pubKey := NewPubKey(keyID, ES256K, secp256k1.GenPrivKey().PubKey())

auth := newAuthentication(keyID)
bz, err := auth.MarshalJSON()
require.NoError(t, err)
require.Equal(t, fmt.Sprintf(`"%v"`, keyID), string(bz))

auth = newAuthenticationDedicated(pubKey)
bz, err = auth.MarshalJSON()
require.NoError(t, err)
regex := fmt.Sprintf(`^{"id":"%v","type":"%v","publicKeyBase58":".+"}$`, keyID, ES256K)
require.Regexp(t, regex, string(bz))
}

func TestAuthentication_UnmarshalJSON(t *testing.T) {
did := DID("did:panacea:testnet:KS5zGZt66Me8MCctZBYrP")
keyID := NewKeyID(did, "key1")
pubKey := NewPubKey(keyID, ES256K, secp256k1.GenPrivKey().PubKey())

var auth Authentication
bz := []byte(fmt.Sprintf(`"%v"`, keyID))
require.NoError(t, auth.UnmarshalJSON(bz))
require.Equal(t, newAuthentication(keyID), auth)
require.True(t, auth.Valid(did))

bz = []byte(fmt.Sprintf(`{"id":"%v","type":"%v","publicKeyBase58":"%v"}`, keyID, ES256K, pubKey.KeyBase58))
require.NoError(t, auth.UnmarshalJSON(bz))
require.Equal(t, newAuthenticationDedicated(pubKey), auth)
require.True(t, auth.Valid(did))
}

func TestDIDDocumentWithSeq_Empty(t *testing.T) {
require.False(t, NewDIDDocumentWithSeq(getValidDIDDocument(), InitialSequence).Empty())
require.True(t, DIDDocumentWithSeq{}.Empty())
Expand Down
4 changes: 2 additions & 2 deletions x/did/types/msgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestMsgCreateDID(t *testing.T) {
require.Equal(t, fromAddr, msg.GetSigners()[0])

require.Equal(t,
`{"type":"did/MsgCreateDID","value":{"did":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","document":{"authentication":["did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1"],"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","publicKey":[{"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","publicKeyBase58":"qoRmLNBEXoaKDE8dKffMq2DBNxacTEfvbKRuFrccYW1b","type":"Secp256k1VerificationKey2018"}]},"from_address":"panacea154p6kyu9kqgvcmq63w3vpn893ssy6anpu8ykfq","sig_key_id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","signature":"bXktc2ln"}}`,
`{"type":"did/MsgCreateDID","value":{"did":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","document":{"@context":"https://www.w3.org/ns/did/v1","authentication":["did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1"],"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","publicKey":[{"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","publicKeyBase58":"qoRmLNBEXoaKDE8dKffMq2DBNxacTEfvbKRuFrccYW1b","type":"Secp256k1VerificationKey2018"}]},"from_address":"panacea154p6kyu9kqgvcmq63w3vpn893ssy6anpu8ykfq","sig_key_id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","signature":"bXktc2ln"}}`,
string(msg.GetSignBytes()),
)
}
Expand All @@ -59,7 +59,7 @@ func TestMsgUpdateDID(t *testing.T) {
require.Equal(t, fromAddr, msg.GetSigners()[0])

require.Equal(t,
`{"type":"did/MsgUpdateDID","value":{"did":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","document":{"authentication":["did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1"],"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","publicKey":[{"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","publicKeyBase58":"qoRmLNBEXoaKDE8dKffMq2DBNxacTEfvbKRuFrccYW1b","type":"Secp256k1VerificationKey2018"}]},"from_address":"panacea154p6kyu9kqgvcmq63w3vpn893ssy6anpu8ykfq","sig_key_id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","signature":"bXktc2ln"}}`,
`{"type":"did/MsgUpdateDID","value":{"did":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","document":{"@context":"https://www.w3.org/ns/did/v1","authentication":["did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1"],"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP","publicKey":[{"id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","publicKeyBase58":"qoRmLNBEXoaKDE8dKffMq2DBNxacTEfvbKRuFrccYW1b","type":"Secp256k1VerificationKey2018"}]},"from_address":"panacea154p6kyu9kqgvcmq63w3vpn893ssy6anpu8ykfq","sig_key_id":"did:panacea:testnet:KS5zGZt66Me8MCctZBYrP#key1","signature":"bXktc2ln"}}`,
string(msg.GetSignBytes()),
)
}
Expand Down

0 comments on commit ac147c3

Please sign in to comment.