comnic's Dev&Life

[DID 구현 연재]1. DID(Decentralized Identifier) 만들기 본문

DID&SSI

[DID 구현 연재]1. DID(Decentralized Identifier) 만들기

comnic 2022. 5. 28. 12:16
반응형

1. DID(Decentralized Identifier) 만들기

이번 글에서는 DID를 Go언어로 간단하게 구현해 보고자 합니다. 글을 시작하기 전에 명확히 구분해야 할 것이 있습니다. 바로 Identity와 Identidier의 구분입니다. 전자는 신원을 의미할 것이고, 후자는 식별자를 의미 합니다. 우리는 분산(탈중앙화) 식별자에 대해 다루고 있습니다. 이는 상당히 큰 차이가 있으며, 실제 구현에 있어서도 중요한 부분이라 먼저 언급하고 시작합니다. 즉, 이번 글에서 우리는 단순히 식별자만을 만들 것입니다. 물론 신원에 대한 연결고리를 향후 계속 가지고 갈 것입니다. 그것들이 DID Document와 Verfiable Credential로 이어질 것입니다. 그럼 시작해 보겠습니다.

DID를 누가 생성할 것인가는 사실 기획의 관점이 아닐까 싶습니다. 단지 W3C 등 표준화 과정에서 논의되고 있는 것은 향후 다루게 될 VDR에서 생성하는 것입니다. 이는 표준화의 입장에서 가장 좋은 접근으로 보입니다. 그러나 이번 연재의 목적은 코드를 구현해 보는 것에 있기에 일단 DID를 생성하는 방법을 보는 것으로 하겠습니다. 이후 이 코드를 VDR로 옮겨 Wallet에 전달해 주는 것은 기획자와 구현하시는 분들에게 맡기고자 합니다. 이렇게 하는 이유는 사실 진행상 편의도 있습니다.

그럼 이제부터 DID를 만들어 보도록 하겠습니다. 앞에서 말씀드렸듯이 기본적인 개념에 대해서 알고 있다는 전제에서 진행합니다.

먼저 DID는 유니크해야 하며, 나중에 서명과 검증에 연관될 수 있어야 하기에 PKI를 기반으로 합니다. 물론 DID 자체가 이를 담고 있거나, PKI에서 파생될 필요는 없습니다. Holder가 Private Key를 가지고 있고, Public Key를 공유할 수 있으면 됩니다. 하여, 크게 2가지 방식으로 구현을 하곤 합니다. PKI와 상관없이 유니크한 키를 생성해서 사용하는 방법과 PKI의 Public Key가 이미 유니크하니 이것을 사용해서 DID를 만드는 방법입니다. 둘 다 장단점이 있어 실제 자신에게 맞게 선택하는 구현할 수 있습니다. 우리는 Public Key에서 파생된 DID를 만들어 사용하겠습니다.

그럼 DID의 형식을 먼저 살펴보도록 하겠습니다. 아래 그림은 W3C에 있는 가장 많이 인용되는 이미지 입니다.

여기서 우리는 DID Method를 정하고 DID Method-Specific Identifier를 생성해서 DID를 만들 것입니다.

DID Method는 제 아이디인 comnic를 사용하고, DID Method-Specific Identifier는 Public key를 hashing 해서 사용하도록 하겠습니다.

아래와 같은 순서로 DID를 만들어 보겠습니다.

  1. PKI 만들기(ecdsa)
  2. ecdsa lib 만들기
  3. DID Method-Specific Identifier를 위한 hash함수 만들기
  4. DID 생성

- PKI 만들기

package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
	"log"
)

func main() {
	pvKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // elliptic.p224, elliptic.P384(), elliptic.P521()

	if err != nil {
		log.Println("ECDSA Keypair generation was Fail!")
	}

	pbKey := &pvKey.PublicKey

	fmt.Printf("########## Key pair ##########\n")
	fmt.Printf("===== Private Key =====\n")
	fmt.Printf("Private Key: %x\n", pvKey.D)
	fmt.Printf("===== Public Key(X, Y) =====\n")
	fmt.Printf("X=%s Y=%s\n", pbKey.X, pbKey.Y)
	fmt.Printf("  Hex: X=%x Y=%x\n\n", pbKey.X.Bytes(), pbKey.Y.Bytes())

}

<실행 결과>

########## Key pair ##########
===== Private Key =====
Private Key: 6d688ebb81488612c019176c5afd6dc3f47d49d428cfcac242e70bb7540a830d
===== Public Key(X, Y) =====
X=11111113476015404431745785688320337553523056530427660169894506217858544854856 Y=31169211335127602530415355066417164383348244433779631439893653999110871979393
  Hex: X=1890aad8765433cf2ee80a07d17aaf71504204a90db11c68a211d90ae85e6748 Y=44e925fff69bbe83db21fd50f8faf7010453b1e77b1fbd36f4aba0c5aa261581

 

- ecdsa lib 만들기

위 코드에서 간단히 ecdsa를 생성하고 각 키의 값을 출력해 보았습니다. 이를 기준으로 좀 더 사용하기 쉽도록 라이브러리로 만들어 보겠습니다. 먼저 core라는 폴더를 만들겠습니다. 그 안에 ecdsa.go라고 파일을 생성하고 아래와 같은 내용들을 채워 보겠습니다.

  • Generate(): 새로운 ecdsa를 생성합니다.
  • ECDSAManager 구조체: 생성한 Private Key와 Public Key를 저장합니다.
  • PublicKeyMultibase(): Public Key를 multibase로 인코딩합니다.
// core/ecdsa.go

package core

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/btcsuite/btcutil/base58"
	"github.com/multiformats/go-multibase"
	"log"
	"math/big"
)

const (
	ELLIPTIC_CURVE = "p256"
)

type ECDSAManager struct {
	PrivateKey *ecdsa.PrivateKey
	PublicKey  *ecdsa.PublicKey
}

func NewEcdsa() (ecdsa *ECDSAManager) {
	ecdsa = new(ECDSAManager)
	err := ecdsa.Generate()
	if err != nil {
		log.Printf("Fail to ECDSA Generate.")
		return nil
	}

	return
}

func (e *ECDSAManager) Generate() error {
	pvKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // elliptic.p224, elliptic.P384(), elliptic.P521()

	if err != nil {
		return errors.New("ECDSA Keypair generation was Fail!")
	}

	e.PrivateKey = pvKey
	e.PublicKey = &pvKey.PublicKey

	return nil
}

func (e *ECDSAManager) PublicKeyMultibase() string {
	if e.PublicKey == nil {
		return ""
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(e.PublicKey)

	if err != nil {
		log.Printf("error occured: %v", err.Error())
		return ""
	}

	str, err := multibase.Encode(multibase.Base58BTC, publicKeyBytes)
	if err != nil {
		log.Printf("error occured: %v", err.Error())
		return ""
	}
	return str
}

- hash 함수 만들기

util폴더에 hash.go 파일을 생성하고 아래와 같이 간단한 hash관련 함수들을 만듭니다.

// util/hash.go

package util

import (
	"crypto/sha256"
	"encoding/hex"
	"github.com/btcsuite/btcutil/base58"
)

func MakeHash(plain string) []byte {
	digest := sha256.Sum256([]byte(plain))
	return digest[:]
}

func MakeHashBase58(plain string) string {
	return base58.Encode(MakeHash(plain))
}

func MakeHashHex(plain string) string {
	return hex.EncodeToString(MakeHash(plain))
}

- DID 만들기

package main

import (
	"errors"
	"fmt"
	"github.com/comnics/did-example/core"
	"github.com/comnics/did-example/util"
	"log"
)

func NewDID(method string, pbKey string) (string, error) {
	if method == "" || pbKey == "" {
		return "", errors.New("parameter is not valid")
	}

	specificIdentifier := util.MakeHashBase58(pbKey)

	// DID:Method:specific
	did := fmt.Sprintf("did:%s:%s", method, specificIdentifier)

	return did, nil
}

func main() {
	var method = "comnic"

	kms := new(core.ECDSAManager)
	kms.Generate()

	did, err := NewDID(method, kms.PublicKeyMultibase())

	if err != nil {
		log.Printf("Failed to generate DID, error: %v\n", err)
	}

	fmt.Println("### New DID ###")
	fmt.Printf("did => %s\n", did)
}

<실행 결과>

### New DID ###
did => did:comnic:ANHnPufjhx3Jnv52GFaPJGdKsvDk1cWVKBcEQ2jbSqwE

 

저는 Goland라는 IDE툴을 사용하고 있습니다. Goland는 아래와 같은 화면입니다.(대표사진 사용을 위한 스크린샷을 한 컷씩 올릴까 합니다. 양해 부탁드립니다.)

이렇게 해서 간단한 DID를 생성해 보았습니다.
다시 정리하면, PKI를 생성하고 Public Key를 이용해 DID Method-Specific Identifier를 만들어 DID를 만들어 보았습니다. PKI의 Private Key와 Public Key는 이후 DID Document와 VP 등에서 계속 사용할 것입니다. 지금은 가장 간단하게 Public Key를 활용해서 유니크한 식별자를 생성했다고 생각하시면 좋을 듯 합니다.

다음 글에서는 DID Document를 만들어 보도록 하겠습니다.

반응형

'DID&SSI' 카테고리의 다른 글

[DID연재]0. 연재를 시작하면서...  (0) 2022.05.28
Comments