github.com/storacha/go-ucanto@v0.7.2/did/did.go (about)

     1  package did
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	mbase "github.com/multiformats/go-multibase"
     9  	varint "github.com/multiformats/go-varint"
    10  )
    11  
    12  const Prefix = "did:"
    13  const KeyPrefix = "did:key:"
    14  
    15  const DIDCore = 0x0d1d
    16  const Ed25519 = 0xed
    17  const RSA = 0x1205
    18  
    19  var MethodOffset = varint.UvarintSize(uint64(DIDCore))
    20  
    21  type DID struct {
    22  	key bool
    23  	str string
    24  }
    25  
    26  // Undef can be used to represent a nil or undefined DID, using DID{}
    27  // directly is also acceptable.
    28  var Undef = DID{}
    29  
    30  func (d DID) Defined() bool {
    31  	return d.str != ""
    32  }
    33  
    34  func (d DID) Bytes() []byte {
    35  	if !d.Defined() {
    36  		return nil
    37  	}
    38  	return []byte(d.str)
    39  }
    40  
    41  func (d DID) DID() DID {
    42  	return d
    43  }
    44  
    45  // String formats the decentralized identity document (DID) as a string.
    46  func (d DID) String() string {
    47  	if d.key {
    48  		key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.str))
    49  		return "did:key:" + key
    50  	}
    51  	return "did:" + d.str[MethodOffset:]
    52  }
    53  
    54  // GoString formats the decentralized identity document (DID) as a string.
    55  func (d DID) GoString() string {
    56  	return d.String()
    57  }
    58  
    59  func (d DID) MarshalJSON() ([]byte, error) {
    60  	if d == Undef {
    61  		return json.Marshal(nil)
    62  	}
    63  	return json.Marshal(d.String())
    64  }
    65  
    66  func (d *DID) UnmarshalJSON(b []byte) error {
    67  	var str string
    68  	err := json.Unmarshal(b, &str)
    69  	if err != nil {
    70  		return fmt.Errorf("parsing string: %w", err)
    71  	}
    72  	if str == "" {
    73  		return nil
    74  	}
    75  	parsed, err := Parse(str)
    76  	if err != nil {
    77  		return fmt.Errorf("parsing DID: %w", err)
    78  	}
    79  	*d = parsed
    80  	return nil
    81  }
    82  
    83  func Decode(bytes []byte) (DID, error) {
    84  	code, _, err := varint.FromUvarint(bytes)
    85  	if err != nil {
    86  		return Undef, err
    87  	}
    88  	if code == Ed25519 || code == RSA {
    89  		return DID{str: string(bytes), key: true}, nil
    90  	} else if code == DIDCore {
    91  		return DID{str: string(bytes)}, nil
    92  	}
    93  	return Undef, fmt.Errorf("unsupported DID encoding: 0x%x", code)
    94  }
    95  
    96  func Parse(str string) (DID, error) {
    97  	if !strings.HasPrefix(str, Prefix) {
    98  		return Undef, fmt.Errorf("must start with 'did:'")
    99  	}
   100  
   101  	if strings.HasPrefix(str, KeyPrefix) {
   102  		code, bytes, err := mbase.Decode(str[len(KeyPrefix):])
   103  		if err != nil {
   104  			return Undef, err
   105  		}
   106  		if code != mbase.Base58BTC {
   107  			return Undef, fmt.Errorf("not Base58BTC encoded")
   108  		}
   109  		return Decode(bytes)
   110  	}
   111  
   112  	buf := make([]byte, MethodOffset)
   113  	varint.PutUvarint(buf, DIDCore)
   114  	suffix, _ := strings.CutPrefix(str, Prefix)
   115  	buf = append(buf, suffix...)
   116  	return DID{str: string(buf)}, nil
   117  }