Files
ucan/did/did.go

115 lines
2.6 KiB
Go
Raw Normal View History

2023-09-13 22:32:07 +01:00
package did
import (
"fmt"
"strings"
crypto "github.com/libp2p/go-libp2p/core/crypto"
2023-09-13 22:32:07 +01:00
mbase "github.com/multiformats/go-multibase"
2024-09-09 08:50:15 -04:00
"github.com/multiformats/go-multicodec"
2023-09-13 22:32:07 +01:00
varint "github.com/multiformats/go-varint"
)
const Prefix = "did:"
const KeyPrefix = "did:key:"
2023-09-19 22:52:23 +01:00
const DIDCore = 0x0d1d
2023-09-13 22:32:07 +01:00
const Ed25519 = 0xed
2024-09-09 08:50:15 -04:00
const RSA = uint64(multicodec.RsaPub)
2023-09-13 22:32:07 +01:00
2023-09-19 22:52:23 +01:00
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
2024-09-09 08:50:15 -04:00
//
// [did:key format]: https://w3c-ccg.github.io/did-method-key/
2023-09-13 22:32:07 +01:00
type DID struct {
2024-09-09 08:50:15 -04:00
key bool
code uint64
str string
2023-09-13 22:32:07 +01:00
}
// Undef can be used to represent a nil or undefined DID, using DID{}
// directly is also acceptable.
var Undef = DID{}
func (d DID) Defined() bool {
return d.str != ""
}
func (d DID) Bytes() []byte {
if !d.Defined() {
return nil
}
return []byte(d.str)
}
2024-09-09 08:50:15 -04:00
func (d DID) Code() uint64 {
return d.code
}
2023-09-13 22:32:07 +01:00
func (d DID) DID() DID {
return d
}
func (d DID) PubKey() (crypto.PubKey, error) {
if !d.key {
return nil, fmt.Errorf("unsupported did type: %s", d.String())
}
unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{
multicodec.Ed25519Pub: crypto.UnmarshalEd25519PublicKey,
multicodec.RsaPub: crypto.UnmarshalRsaPublicKey,
multicodec.Secp256k1Pub: crypto.UnmarshalSecp256k1PublicKey,
multicodec.Es256: crypto.UnmarshalECDSAPublicKey,
}[multicodec.Code(d.code)]
if !ok {
return nil, fmt.Errorf("unsupported multicodec: %d", d.code)
}
return unmarshaler(d.Bytes()[varint.UvarintSize(d.code):])
}
2023-12-01 17:32:34 +00:00
// String formats the decentralized identity document (DID) as a string.
2023-09-13 22:32:07 +01:00
func (d DID) String() string {
2023-09-19 22:52:23 +01:00
if d.key {
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
return "did:key:" + key
}
return "did:" + d.str[MethodOffset:]
2023-09-13 22:32:07 +01:00
}
func Decode(bytes []byte) (DID, error) {
code, _, err := varint.FromUvarint(bytes)
if err != nil {
return Undef, err
}
2024-09-09 08:50:15 -04:00
if code == Ed25519 || code == RSA {
return DID{str: string(bytes), code: code, key: true}, nil
2023-09-19 22:52:23 +01:00
} else if code == DIDCore {
2023-09-13 22:32:07 +01:00
return DID{str: string(bytes)}, nil
}
2023-09-22 22:53:47 +01:00
return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code)
2023-09-13 22:32:07 +01:00
}
func Parse(str string) (DID, error) {
if !strings.HasPrefix(str, Prefix) {
2023-09-22 22:53:47 +01:00
return Undef, fmt.Errorf("must start with 'did:'")
2023-09-13 22:32:07 +01:00
}
if strings.HasPrefix(str, KeyPrefix) {
code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
if err != nil {
return Undef, err
}
if code != mbase.Base58BTC {
2023-09-22 22:53:47 +01:00
return Undef, fmt.Errorf("not Base58BTC encoded")
2023-09-13 22:32:07 +01:00
}
return Decode(bytes)
}
2023-09-19 22:52:23 +01:00
buf := make([]byte, MethodOffset)
varint.PutUvarint(buf, DIDCore)
suffix, _ := strings.CutPrefix(str, Prefix)
buf = append(buf, suffix...)
return DID{str: string(buf)}, nil
2023-09-13 22:32:07 +01:00
}