Merge pull request #11 from INFURA/jwk

complete support for did:key, extensive abstraction layer for crypto handling
This commit is contained in:
Michael Muré
2025-07-10 15:48:21 +02:00
committed by GitHub
71 changed files with 5898 additions and 305 deletions

View File

@@ -0,0 +1,38 @@
package allkeys
import (
"fmt"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/ed25519"
helpers "github.com/INFURA/go-did/crypto/internal"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/p384"
"github.com/INFURA/go-did/crypto/p521"
"github.com/INFURA/go-did/crypto/rsa"
"github.com/INFURA/go-did/crypto/secp256k1"
"github.com/INFURA/go-did/crypto/x25519"
)
var decoders = map[uint64]func(b []byte) (crypto.PublicKey, error){
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
p384.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p384.PublicKeyFromBytes(b) },
p521.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p521.PublicKeyFromBytes(b) },
rsa.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return rsa.PublicKeyFromPKCS1DER(b) },
secp256k1.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return secp256k1.PublicKeyFromBytes(b) },
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its PublicKeyMultibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (crypto.PublicKey, error) {
code, pubBytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
decoder, ok := decoders[code]
if !ok {
return nil, fmt.Errorf("unsupported publicKeyMultibase code: %d", code)
}
return decoder(pubBytes)
}

View File

@@ -29,6 +29,9 @@ type TestHarness[PubT crypto.PublicKey, PrivT crypto.PrivateKey] struct {
MultibaseCode uint64
DefaultHash crypto.Hash
OtherHashes []crypto.Hash
PublicKeyBytesSize int
PrivateKeyBytesSize int
SignatureBytesSize int
@@ -78,8 +81,14 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
})
t.Run("Equality", func(t *testing.T) {
if !pubImplements[PubT, crypto.PublicKeyToBytes]() {
t.Skip("Public key does not implement crypto.PublicKeyToBytes")
}
pub1, priv1, err := harness.GenerateKeyPair()
require.NoError(t, err)
pub1Tb := (crypto.PublicKey(pub1)).(crypto.PublicKeyToBytes)
priv1Tb := (crypto.PrivateKey(priv1)).(crypto.PrivateKeyToBytes)
pub2, priv2, err := harness.GenerateKeyPair()
require.NoError(t, err)
@@ -88,29 +97,35 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
require.False(t, pub1.Equal(pub2))
require.False(t, priv1.Equal(priv2))
pub1copy, err := harness.PublicKeyFromBytes(pub1.ToBytes())
pub1copy, err := harness.PublicKeyFromBytes(pub1Tb.ToBytes())
require.NoError(t, err)
require.True(t, pub1.Equal(pub1copy))
require.True(t, pub1copy.Equal(pub1))
priv1copy, err := harness.PrivateKeyFromBytes(priv1.ToBytes())
priv1copy, err := harness.PrivateKeyFromBytes(priv1Tb.ToBytes())
require.NoError(t, err)
require.True(t, priv1.Equal(priv1copy))
require.True(t, priv1copy.Equal(priv1))
})
t.Run("BytesRoundTrip", func(t *testing.T) {
if !pubImplements[PubT, crypto.PublicKeyToBytes]() {
t.Skip("Public key does not implement crypto.PublicKeyToBytes")
}
pub, priv, err := harness.GenerateKeyPair()
require.NoError(t, err)
pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes)
privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes)
bytes := pub.ToBytes()
bytes := pubTb.ToBytes()
stats.bytesPubSize = len(bytes)
rtPub, err := harness.PublicKeyFromBytes(bytes)
require.NoError(t, err)
require.True(t, pub.Equal(rtPub))
require.Equal(t, harness.PublicKeyBytesSize, len(bytes))
bytes = priv.ToBytes()
bytes = privTb.ToBytes()
stats.bytesPrivSize = len(bytes)
rtPriv, err := harness.PrivateKeyFromBytes(bytes)
require.NoError(t, err)
@@ -175,54 +190,92 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
pub, priv, err := harness.GenerateKeyPair()
require.NoError(t, err)
spub, ok := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
if !ok {
t.Skip("Signature is not implemented")
}
spriv, ok := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
if !ok {
t.Skip("Signature is not implemented")
}
for _, tc := range []struct {
type testcase struct {
name string
signer func(msg []byte) ([]byte, error)
verifier func(msg []byte, sig []byte) bool
signer func(msg []byte, opts ...crypto.SigningOption) ([]byte, error)
verifier func(msg []byte, sig []byte, opts ...crypto.SigningOption) bool
expectedSize int
stats *int
}{
{
name: "Bytes signature",
signer: spriv.SignToBytes,
verifier: spub.VerifyBytes,
expectedSize: harness.SignatureBytesSize,
stats: &stats.sigRawSize,
},
{
name: "ASN.1 signature",
signer: spriv.SignToASN1,
verifier: spub.VerifyASN1,
stats: &stats.sigAsn1Size,
},
} {
defaultHash crypto.Hash
otherHashes []crypto.Hash
}
var tcs []testcase
if pubImplements[PubT, crypto.PublicKeySigningBytes]() {
t.Run("Bytes signature", func(t *testing.T) {
spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningBytes)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes)
tcs = append(tcs, testcase{
name: "Bytes signature",
signer: spriv.SignToBytes,
verifier: spub.VerifyBytes,
expectedSize: harness.SignatureBytesSize,
stats: &stats.sigRawSize,
defaultHash: harness.DefaultHash,
otherHashes: harness.OtherHashes,
})
})
}
if pubImplements[PubT, crypto.PublicKeySigningASN1]() {
t.Run("ASN.1 signature", func(t *testing.T) {
spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningASN1)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1)
tcs = append(tcs, testcase{
name: "ASN.1 signature",
signer: spriv.SignToASN1,
verifier: spub.VerifyASN1,
stats: &stats.sigAsn1Size,
defaultHash: harness.DefaultHash,
otherHashes: harness.OtherHashes,
})
})
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
msg := []byte("message")
sig, err := tc.signer(msg)
sigNoParams, err := tc.signer(msg)
require.NoError(t, err)
require.NotEmpty(t, sigNoParams)
sigDefault, err := tc.signer(msg, crypto.WithSigningHash(tc.defaultHash))
require.NoError(t, err)
require.NotEmpty(t, sig)
if tc.expectedSize > 0 {
require.Equal(t, tc.expectedSize, len(sig))
require.Equal(t, tc.expectedSize, len(sigNoParams))
}
*tc.stats = len(sig)
*tc.stats = len(sigNoParams)
valid := tc.verifier(msg, sig)
// signatures might be different (i.e. non-deterministic), but they should verify the same way
valid := tc.verifier(msg, sigNoParams)
require.True(t, valid)
valid = tc.verifier(msg, sigDefault)
require.True(t, valid)
valid = tc.verifier([]byte("wrong message"), sig)
valid = tc.verifier([]byte("wrong message"), sigNoParams)
require.False(t, valid)
valid = tc.verifier([]byte("wrong message"), sigDefault)
require.False(t, valid)
})
for _, hash := range tc.otherHashes {
t.Run(fmt.Sprintf("%s-%s", tc.name, hash.String()), func(t *testing.T) {
msg := []byte("message")
sig, err := tc.signer(msg, crypto.WithSigningHash(hash))
require.NoError(t, err)
require.NotEmpty(t, sig)
valid := tc.verifier(msg, sig, crypto.WithSigningHash(hash))
require.True(t, valid)
valid = tc.verifier([]byte("wrong message"), sig)
require.False(t, valid)
})
}
}
})
@@ -234,11 +287,11 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
pub3, _, err := harness.GenerateKeyPair()
require.NoError(t, err)
kePriv1, ok := crypto.PrivateKey(priv1).(crypto.KeyExchangePrivateKey)
kePriv1, ok := crypto.PrivateKey(priv1).(crypto.PrivateKeyKeyExchange)
if !ok {
t.Skip("Key exchange is not implemented")
}
kePriv2 := crypto.PrivateKey(priv2).(crypto.KeyExchangePrivateKey)
kePriv2 := crypto.PrivateKey(priv2).(crypto.PrivateKeyKeyExchange)
// TODO: test with incompatible public keys
require.True(t, kePriv1.PublicKeyIsCompatible(pub2))
@@ -271,20 +324,26 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Bytes", func(b *testing.B) {
if !pubImplements[PubT, crypto.PublicKeyToBytes]() {
b.Skip("Public key does not implement crypto.PublicKeyToBytes")
}
b.Run("PubToBytes", func(b *testing.B) {
pub, _, err := harness.GenerateKeyPair()
require.NoError(b, err)
pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = pub.ToBytes()
_ = pubTb.ToBytes()
}
})
b.Run("PubFromBytes", func(b *testing.B) {
pub, _, err := harness.GenerateKeyPair()
require.NoError(b, err)
buf := pub.ToBytes()
pubTb := (crypto.PublicKey(pub)).(crypto.PublicKeyToBytes)
buf := pubTb.ToBytes()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
@@ -295,17 +354,19 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
b.Run("PrivToBytes", func(b *testing.B) {
_, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = priv.ToBytes()
_ = privTb.ToBytes()
}
})
b.Run("PrivFromBytes", func(b *testing.B) {
_, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
buf := priv.ToBytes()
privTb := (crypto.PrivateKey(priv)).(crypto.PrivateKeyToBytes)
buf := privTb.ToBytes()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
@@ -403,15 +464,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Signatures", func(b *testing.B) {
if _, ok := (crypto.PublicKey(*new(PubT))).(crypto.SigningPublicKey); !ok {
b.Skip("Signature is not implemented")
}
b.Run("Sign to Bytes signature", func(b *testing.B) {
if !pubImplements[PubT, crypto.PublicKeySigningBytes]() {
b.Skip("Signature to bytes is not implemented")
}
_, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes)
b.ResetTimer()
b.ReportAllocs()
@@ -422,11 +483,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Verify from Bytes signature", func(b *testing.B) {
if !pubImplements[PubT, crypto.PublicKeySigningBytes]() {
b.Skip("Signature to bytes is not implemented")
}
pub, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningBytes)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes)
sig, err := spriv.SignToBytes([]byte("message"))
require.NoError(b, err)
@@ -439,10 +504,14 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Sign to ASN.1 signature", func(b *testing.B) {
if !pubImplements[PubT, crypto.PublicKeySigningASN1]() {
b.Skip("Signature to ASN.1 is not implemented")
}
_, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1)
b.ResetTimer()
b.ReportAllocs()
@@ -453,11 +522,15 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Verify from ASN.1 signature", func(b *testing.B) {
if !pubImplements[PubT, crypto.PublicKeySigningASN1]() {
b.Skip("Signature to ASN.1 is not implemented")
}
pub, priv, err := harness.GenerateKeyPair()
require.NoError(b, err)
spub := (crypto.PublicKey(pub)).(crypto.SigningPublicKey)
spriv := (crypto.PrivateKey(priv)).(crypto.SigningPrivateKey)
spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningASN1)
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningASN1)
sig, err := spriv.SignToASN1([]byte("message"))
require.NoError(b, err)
@@ -471,14 +544,14 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
b.Run("Key exchange", func(b *testing.B) {
if _, ok := (crypto.PrivateKey(*new(PrivT))).(crypto.KeyExchangePrivateKey); !ok {
b.Skip("Key echange is not implemented")
if !privImplements[PrivT, crypto.PrivateKeyKeyExchange]() {
b.Skip("Key exchange is not implemented")
}
b.Run("KeyExchange", func(b *testing.B) {
_, priv1, err := harness.GenerateKeyPair()
require.NoError(b, err)
kePriv1 := (crypto.PrivateKey(priv1)).(crypto.KeyExchangePrivateKey)
kePriv1 := (crypto.PrivateKey(priv1)).(crypto.PrivateKeyKeyExchange)
pub2, _, err := harness.GenerateKeyPair()
require.NoError(b, err)
@@ -491,3 +564,13 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
})
})
}
func privImplements[PrivT crypto.PrivateKey, wanted crypto.PrivateKey]() bool {
_, ok := crypto.PrivateKey(*new(PrivT)).(wanted)
return ok
}
func pubImplements[PubT crypto.PublicKey, wanted crypto.PublicKey]() bool {
_, ok := crypto.PublicKey(*new(PubT)).(wanted)
return ok
}

View File

@@ -3,6 +3,7 @@ package ed25519
import (
"testing"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
@@ -17,6 +18,8 @@ var harness = testsuite.TestHarness[PublicKey, PrivateKey]{
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA512,
OtherHashes: nil,
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,

View File

@@ -11,7 +11,9 @@ import (
"github.com/INFURA/go-did/crypto"
)
var _ crypto.SigningPrivateKey = &PrivateKey{}
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
var _ crypto.PrivateKeyToBytes = &PrivateKey{}
type PrivateKey struct {
k ed25519.PrivateKey
@@ -28,13 +30,24 @@ func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
return PrivateKey{k: append([]byte{}, b...)}, nil
}
func PrivateKeyFromSeed(seed []byte) (PrivateKey, error) {
if len(seed) != ed25519.SeedSize {
return PrivateKey{}, fmt.Errorf("invalid ed25519 seed size")
}
return PrivateKey{k: ed25519.NewKeyFromSeed(seed)}, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (PrivateKey, error) {
priv, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return PrivateKey{}, err
}
return PrivateKey{k: priv.(ed25519.PrivateKey)}, nil
edPriv, ok := priv.(ed25519.PrivateKey)
if !ok {
return PrivateKey{}, fmt.Errorf("invalid private key type")
}
return PrivateKey{k: edPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
@@ -60,13 +73,21 @@ func (p PrivateKey) Public() crypto.PublicKey {
return PublicKey{k: p.k.Public().(ed25519.PublicKey)}
}
func (p PrivateKey) SignToBytes(message []byte) ([]byte, error) {
func (p PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
return nil, fmt.Errorf("ed25519 does not support custom hash functions")
}
return ed25519.Sign(p.k, message), nil
}
// SignToASN1 creates a signature with ASN.1 encoding.
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
func (p PrivateKey) SignToASN1(message []byte) ([]byte, error) {
func (p PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
return nil, fmt.Errorf("ed25519 does not support custom hash functions")
}
sig := ed25519.Sign(p.k, message)
var b cryptobyte.Builder
b.AddASN1BitString(sig)

View File

@@ -10,10 +10,12 @@ import (
"golang.org/x/crypto/cryptobyte"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_helpers"
"github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.SigningPublicKey = &PublicKey{}
var _ crypto.PublicKeySigningBytes = PublicKey{}
var _ crypto.PublicKeySigningASN1 = PublicKey{}
var _ crypto.PublicKeyToBytes = PublicKey{}
type PublicKey struct {
k ed25519.PublicKey
@@ -97,13 +99,23 @@ func (p PublicKey) Equal(other crypto.PublicKey) bool {
return false
}
func (p PublicKey) VerifyBytes(message, signature []byte) bool {
func (p PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
// ed25519 does not support custom hash functions
return false
}
return ed25519.Verify(p.k, message, signature)
}
// VerifyASN1 verifies a signature with ASN.1 encoding.
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
func (p PublicKey) VerifyASN1(message, signature []byte) bool {
func (p PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
// ed25519 does not support custom hash functions
return false
}
var s cryptobyte.String = signature
var bitString asn1.BitString

102
crypto/hash.go Normal file
View File

@@ -0,0 +1,102 @@
package crypto
import (
stdcrypto "crypto"
"hash"
"strconv"
"golang.org/x/crypto/sha3"
)
// As the standard crypto library prohibits from registering additional hash algorithm (like keccak),
// below is essentially an extension of that mechanism to allow it.
func init() {
RegisterHash(KECCAK_256, sha3.NewLegacyKeccak256)
RegisterHash(KECCAK_512, sha3.NewLegacyKeccak512)
}
type Hash uint
// HashFunc simply returns the value of h so that [Hash] implements [SignerOpts].
func (h Hash) HashFunc() Hash {
return h
}
func (h Hash) String() string {
if h < maxStdHash {
return stdcrypto.Hash(h).String()
}
// Extensions
switch h {
case KECCAK_256:
return "Keccak-256"
case KECCAK_512:
return "Keccak-512"
default:
return "unknown hash value " + strconv.Itoa(int(h))
}
}
const (
// From "crypto"
MD4 Hash = 1 + iota // import golang.org/x/crypto/md4
MD5 // import crypto/md5
SHA1 // import crypto/sha1
SHA224 // import crypto/sha256
SHA256 // import crypto/sha256
SHA384 // import crypto/sha512
SHA512 // import crypto/sha512
MD5SHA1 // no implementation; MD5+SHA1 used for TLS RSA
RIPEMD160 // import golang.org/x/crypto/ripemd160
SHA3_224 // import golang.org/x/crypto/sha3
SHA3_256 // import golang.org/x/crypto/sha3
SHA3_384 // import golang.org/x/crypto/sha3
SHA3_512 // import golang.org/x/crypto/sha3
SHA512_224 // import crypto/sha512
SHA512_256 // import crypto/sha512
BLAKE2s_256 // import golang.org/x/crypto/blake2s
BLAKE2b_256 // import golang.org/x/crypto/blake2b
BLAKE2b_384 // import golang.org/x/crypto/blake2b
BLAKE2b_512 // import golang.org/x/crypto/blake2b
maxStdHash
// Extensions
KECCAK_256
KECCAK_512
maxHash
)
var hashes = make([]func() hash.Hash, maxHash-maxStdHash-1)
// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
if h > 0 && h < maxStdHash {
return stdcrypto.Hash(h).New()
}
if h > maxStdHash && h < maxHash {
f := hashes[h-maxStdHash-1]
if f != nil {
return f()
}
}
panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
}
// RegisterHash registers a function that returns a new instance of the given
// hash function. This is intended to be called from the init function in
// packages that implement hash functions.
func RegisterHash(h Hash, f func() hash.Hash) {
if h >= maxHash {
panic("crypto: RegisterHash of unknown hash function")
}
if h <= maxStdHash {
panic("crypto: RegisterHash of standard hash function")
}
hashes[h-maxStdHash-1] = f
}

View File

@@ -1,14 +1,11 @@
package crypto
// Public Key
type PublicKey interface {
// Equal returns true if other is the same PublicKey
Equal(other PublicKey) bool
// ToBytes serializes the PublicKey into "raw bytes", without metadata or structure.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
ToBytes() []byte
// ToPublicKeyMultibase format the PublicKey into a string compatible with a PublicKeyMultibase field
// in a DID Document.
ToPublicKeyMultibase() string
@@ -20,6 +17,33 @@ type PublicKey interface {
ToX509PEM() string
}
type PublicKeyToBytes interface {
PublicKey
// ToBytes serializes the PublicKey into "raw bytes", without metadata or structure.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
ToBytes() []byte
}
type PublicKeySigningBytes interface {
PublicKey
// VerifyBytes checks a signature in the "raw bytes" format.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
VerifyBytes(message, signature []byte, opts ...SigningOption) bool
}
type PublicKeySigningASN1 interface {
PublicKey
// VerifyASN1 checks a signature in the ASN.1 format.
VerifyASN1(message, signature []byte, opts ...SigningOption) bool
}
// Private Key
type PrivateKey interface {
// Equal returns true if other is the same PrivateKey
Equal(other PrivateKey) bool
@@ -27,11 +51,6 @@ type PrivateKey interface {
// Public returns the matching PublicKey.
Public() PublicKey
// ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
ToBytes() []byte
// ToPKCS8DER serializes the PrivateKey into the PKCS#8 DER (binary) format.
ToPKCS8DER() []byte
@@ -39,31 +58,32 @@ type PrivateKey interface {
ToPKCS8PEM() string
}
type SigningPublicKey interface {
PublicKey
type PrivateKeyToBytes interface {
PrivateKey
// VerifyBytes checks a signature in the "raw bytes" format.
// ToBytes serializes the PrivateKey into "raw bytes", without metadata or structure.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
VerifyBytes(message, signature []byte) bool
// VerifyASN1 checks a signature in the ASN.1 format.
VerifyASN1(message, signature []byte) bool
ToBytes() []byte
}
type SigningPrivateKey interface {
type PrivateKeySigningBytes interface {
PrivateKey
// SignToBytes creates a signature in the "raw bytes" format.
// This format can make some assumptions and may not be what you expect.
// Ideally, this format is defined by the same specification as the underlying crypto scheme.
SignToBytes(message []byte) ([]byte, error)
// SignToASN1 creates a signature in the ASN.1 format.
SignToASN1(message []byte) ([]byte, error)
SignToBytes(message []byte, opts ...SigningOption) ([]byte, error)
}
type KeyExchangePrivateKey interface {
type PrivateKeySigningASN1 interface {
PrivateKey
// SignToASN1 creates a signature in the ASN.1 format.
SignToASN1(message []byte, opts ...SigningOption) ([]byte, error)
}
type PrivateKeyKeyExchange interface {
PrivateKey
// PublicKeyIsCompatible checks that the given PublicKey is compatible to perform key exchange.

37
crypto/internal/asn1.go Normal file
View File

@@ -0,0 +1,37 @@
package helpers
import (
"errors"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
// Taken from crypto/ecdsa
func EncodeSignatureToASN1(r, s []byte) ([]byte, error) {
var b cryptobyte.Builder
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
addASN1IntBytes(b, r)
addASN1IntBytes(b, s)
})
return b.Bytes()
}
// addASN1IntBytes encodes in ASN.1 a positive integer represented as
// a big-endian byte slice with zero or more leading zeroes.
func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
for len(bytes) > 0 && bytes[0] == 0 {
bytes = bytes[1:]
}
if len(bytes) == 0 {
b.SetError(errors.New("invalid integer"))
return
}
b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {
if bytes[0]&0x80 != 0 {
c.AddUint8(0)
}
c.AddBytes(bytes)
})
}

216
crypto/jwk/private.go Normal file
View File

@@ -0,0 +1,216 @@
package jwk
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/ed25519"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/p384"
"github.com/INFURA/go-did/crypto/p521"
"github.com/INFURA/go-did/crypto/rsa"
"github.com/INFURA/go-did/crypto/secp256k1"
"github.com/INFURA/go-did/crypto/x25519"
)
type PrivateJwk struct {
Privkey crypto.PrivateKey
}
func (pj PrivateJwk) MarshalJSON() ([]byte, error) {
switch privkey := pj.Privkey.(type) {
case ed25519.PrivateKey:
pubkey := privkey.Public().(ed25519.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
D string `json:"d"`
}{
Kty: "OKP",
Crv: "Ed25519",
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.Seed()),
})
case *p256.PrivateKey:
pubkey := privkey.Public().(*p256.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
D string `json:"d"`
}{
Kty: "EC",
Crv: "P-256",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()),
})
case *p384.PrivateKey:
pubkey := privkey.Public().(*p384.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
D string `json:"d"`
}{
Kty: "EC",
Crv: "P-384",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()),
})
case *p521.PrivateKey:
pubkey := privkey.Public().(*p521.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
D string `json:"d"`
}{
Kty: "EC",
Crv: "P-521",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()),
})
case *rsa.PrivateKey:
pubkey := privkey.Public().(*rsa.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
N string `json:"n"`
E string `json:"e"`
D string `json:"d"`
P string `json:"p"`
Q string `json:"q"`
Dp string `json:"dp"`
Dq string `json:"dq"`
Qi string `json:"qi"`
}{
Kty: "RSA",
N: base64.RawURLEncoding.EncodeToString(pubkey.NBytes()),
E: base64.RawURLEncoding.EncodeToString(pubkey.EBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.DBytes()),
P: base64.RawURLEncoding.EncodeToString(privkey.PBytes()),
Q: base64.RawURLEncoding.EncodeToString(privkey.QBytes()),
Dp: base64.RawURLEncoding.EncodeToString(privkey.DpBytes()),
Dq: base64.RawURLEncoding.EncodeToString(privkey.DqBytes()),
Qi: base64.RawURLEncoding.EncodeToString(privkey.QiBytes()),
})
case *secp256k1.PrivateKey:
pubkey := privkey.Public().(*secp256k1.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
D string `json:"d"`
}{
Kty: "EC",
Crv: "secp256k1",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()),
})
case *x25519.PrivateKey:
pubkey := privkey.Public().(*x25519.PublicKey)
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
D string `json:"d"`
}{
Kty: "OKP",
Crv: "X25519",
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()),
})
default:
return nil, fmt.Errorf("unsupported key type %T", privkey)
}
}
func (pj *PrivateJwk) UnmarshalJSON(bytes []byte) error {
aux := make(map[string]string)
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
switch aux["kty"] {
case "EC": // Elliptic curve
// we only use D, ignore X/Y which will be recomputed from D
d, err := base64.RawURLEncoding.DecodeString(aux["d"])
if err != nil {
return fmt.Errorf("invalid d parameter with kty=EC: %w", err)
}
switch aux["crv"] {
case "P-256":
pj.Privkey, err = p256.PrivateKeyFromBytes(d)
return err
case "P-384":
pj.Privkey, err = p384.PrivateKeyFromBytes(d)
return err
case "P-521":
pj.Privkey, err = p521.PrivateKeyFromBytes(d)
return err
case "secp256k1":
pj.Privkey, err = secp256k1.PrivateKeyFromBytes(d)
return err
default:
return fmt.Errorf("unsupported Curve %s", aux["crv"])
}
case "RSA":
// we only use N,E,D,P,Q ignore Dp/Dq/Qi which will be recomputed from other parameters
n, err := base64.RawURLEncoding.DecodeString(aux["n"])
if err != nil {
return fmt.Errorf("invalid n parameter with kty=RSA: %w", err)
}
e, err := base64.RawURLEncoding.DecodeString(aux["e"])
if err != nil {
return fmt.Errorf("invalid e parameter with kty=RSA: %w", err)
}
d, err := base64.RawURLEncoding.DecodeString(aux["d"])
if err != nil {
return fmt.Errorf("invalid d parameter with kty=RSA: %w", err)
}
p, err := base64.RawURLEncoding.DecodeString(aux["p"])
if err != nil {
return fmt.Errorf("invalid p parameter with kty=RSA: %w", err)
}
q, err := base64.RawURLEncoding.DecodeString(aux["q"])
if err != nil {
return fmt.Errorf("invalid q parameter with kty=RSA: %w", err)
}
pj.Privkey, err = rsa.PrivateKeyFromNEDPQ(n, e, d, p, q)
return err
case "OKP": // Octet key pair
d, err := base64.RawURLEncoding.DecodeString(aux["d"])
if err != nil {
return fmt.Errorf("invalid x parameter with kty=OKP: %w", err)
}
switch aux["crv"] {
case "Ed25519":
pj.Privkey, err = ed25519.PrivateKeyFromSeed(d)
return err
case "X25519":
pj.Privkey, err = x25519.PrivateKeyFromBytes(d)
return err
default:
return fmt.Errorf("unsupported Curve %s", aux["crv"])
}
default:
return fmt.Errorf("unsupported key type %s", aux["kty"])
}
}

114
crypto/jwk/private_test.go Normal file
View File

@@ -0,0 +1,114 @@
package jwk
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
// Origin: https://github.com/w3c-ccg/did-key-spec/tree/main/test-vectors
func TestPrivateJwkRoundtrip(t *testing.T) {
for _, tc := range []struct {
name string
in string
}{
{
name: "RSA-2048",
in: `{
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB",
"d": "Eym3sT4KLwBzo5pl5nY83-hAti92iLQRizkrKe22RbNi9Y1kKOBatdtGaJqFVztZZu5ERGKNuTd5VdsjJeekSbXviVGRtdHNCvgmRZlWA5261AgIUPxMmKW062GmGJbKQvscFfziBgHK6tyDBd8cZavqMFHi-7ilMYF7IsFBcJKM85x_30pnfd4YwhGQIc9hzv238aOwYKg8c-MzYhEVUnL273jaiLVlfZWQ5ca-GXJHmdOb_Y4fE5gpXfPFBseqleXsMp0VuXxCEsN30LIJHYscdPtbzLD3LFbuMJglFbQqYqssqymILGqJ7Tc2mB2LmXevfqRWz5D7A_K1WzvuoQ",
"p": "3CWT55Vc9CmUKavtV11fwwCU3lha99eRsl7Yo6HJLudpKV8zJ5bojTPqrowAjiHxyz3ITPCY3WgSX_q1n_4093U51rYermMfHQmrY_l7EgwxvNNMdsH4uMwHhz5vUNks6svtmkJL4JwQe8HPimHMdruCrPZKs0gajO59uNL-0rk",
"q": "zqcpEWpGAeJS0ZzTElrClcl6iQaElAV-KcOVqSOSm25FA2_QE7Px9FTGTrPDBivH5P9iT7SgAWwPypYiCJeDxZ_Rt1FbQvR0gfhzp9_eZJERd4BPaHdcNoXQXVgqezTxbJha064iJhYKHI72zB4rsBS5o4n7qWvqUSXNMYdV_6U",
"dp": "gcUE8rZxHNyFoiretWktUd2943Nh7Eb-c47FVW_BEA0JSIH9vZCPdOztogaVLTOFPLEmqXQKKDl422sGNVG8F0La3V5tp452gL96cGxXx8O4bf6ATGD7JLPgnDCJnbbna2Daptv9rmFQtiMBHCmaRUMzPJHSZuxR-lF7er-lxsE",
"dq": "Id2bCVOVLXHdiKReor9k7A8cmaAL0gYkasu2lwVRXU9w1-NXAiOXHydVaEhlSXmbRJflkJJVNmZzIAwCf830tko-oAAhKJPPFA2XRoeVdn2fkynf2YrV_cloICP2skI23kkJeW8sAXnTJmL3ZvP6zNxYn8hZCaa5u5qqSdeX7FE",
"qi": "WKIToXXnjl7GDbz7jCNbX9nWYOE5BDNzVmwiVOnyGoTZfwJ_qtgizj7pOapxi6dT9S9mMavmeAi6LAsEe1WUWtaKSNhbNh0PUGGXlXHGlhkS8jI1ot0e-scrHAuACE567YQ4VurpNorPKtZ5UENXIn74DEmt4l5m6902VF3X5Wo"
}`,
},
{
name: "RSA-4096",
in: `{
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB",
"d": "TMq1H-clVG7PihkjCqJbRFLMj9wmx6_qfauYwPBKK-HYfWujdW5vxBO6Q-jpqy7RxhiISmxYCBVuw_BuKMqQtR8Q_G9StBzaWYjHfn3Vp6Poz4umLqOjbI2NWNks_ybpGbd30oAK8V5ZkO04ozJpkN4i92hzK3mIc5-z1HiTNUPMn6cStab0VCn6em_ylltV774CEcRJ3OLgid7OUspRt_rID3qyreYbOulTu5WXHIGEnZDzrciIlz1dbcVldpUhD0VAP5ZErD2uUP5oztBNcTTn0YBF8CrOALuQVdaz_t_sNS3P0kWeT1eQ0QwDskO5Hw-Aey2tFeWk1bQyLoQ1A0jsw8mDbkO2zrGfJoxmVBkueTK-q64_n1kV7W1aeJFRj4NwEWmwcrs8GSOGOn38fGB_Y3Kci04qvD6L0QZbFkAVzcJracnxbTdHCEX0jsAAPbYC8M_8PyrPJvPC4IAAWTRrSRbysb7r7viRf4A1vTK9VT7uYyxj7Kzx2cU12d9QBXYfdQ2744bUE7HqN-Vh2rHvv2l5v6vzBRoZ5_OhHHVeUYwC9LouE9lSVAObbFM-Qe1SvzbbwN91LziI7UzUc_xMAEiNwt6PpnIAWAhdvSRawEllTwUcn89udHd5UhiAcm-RQOqXIdA9Aly6d8TT8R1p-ZnQ_gbZyBZeS39AuvU",
"p": "1p4cypsJeTyVXXc5bQpvzVenPy78OHXtGcFQnbTjW8x1GsvJ-rlHAcjUImd44pgNQNe-iYpeUg3KqfONeedNgQCFd8kP7GoVAd45mEvsGBXvjoCXOBMQlsf8UU_hm_LKhVvTvTmMGoudnNv5qYNDMCGJGzwoG-aSvROlIoXzHmDnusZ-hKsDxM9j0PPz21t99Y_Fr30Oq3FIWXPVmLYmfyZYQkxm9a9WNMkqRbwJuMwGI6V9ABsQ1dW_KJzp_aEBbJLcDr9DsWhm9ErLeAlzyaDYEai6wCtKm9em4LDwCbKhJq3hWEp1sIG-hwx1sk7N4i-b8lBijjEQE-dbSQxUlw",
"q": "yUqMejfrttGujadj7Uf7q91KM7nbQGny4TjD-CqibcFE-s2_DExCgP1wfhUPfJr2uPQDIe4g12uaNoa5GbCSDaQwEmQpurC_5mazt-z-_tbI24hoPQm5Hq67fZz-jDE_3OccLPLIWtajJqmxHbbB5VqskMuXo8KDxPRfBQBhykmb9_5M8pY2ggZOV4shCUn5E9nOnvibvw5Wx4CBtWUtca4rhpd3mVen1d8xCe4xTG_ni_w1lwdxzU1GmRFqgTuZWzL0r2FKzJg7hju1SOEe4tKMxQ-xs2HyNaMM__SLsNmS3lsYZ8r2hqcjEMQQZI0T_O-3BjIpyg986P8j055E0w",
"dp": "DujzJRw6P0L3OYQT6EBmXgSt6NTRzvZaX4SvnhU4CmOc6xynTpTamwQhwLYhjtRzb0LNyO5k-RxeLQpvlL1-A-1OWHEOeyUvim6u36a-ozm659KFLu8cIu2H2PpMuTHX4gXsIuRBmIKEk6YwpRcqbsiVpt-6BZ4yKZKY0Vou9rhSwQYTOhJLc7vYumaIVX_4szumxzdP8pcvKI_EkhRtfj3iudBnAsCIo6gqGKgkoMMD1iwkEALRW5m66w5jrywlVi6pvRiKkmOna2da1V8KvUJAYJGxT7JyP3tu64M_Wd0gFvjTg_fAT1_kJau27YlOAl2-Xso43poH_OoAzIVfxw",
"dq": "XI6Z76z9BxB9mgcpTLc3wzw63XQNnB3bn7JRcjBwhdVD2at3uLjsL5HaAy-98kbzQfJ56kUr9sI0o_Po8yYc0ob3z80c3wpdAx2gb-dbDWVH8KJVhBOPestPzR--cEpJGlNuwkBU3mgplyKaHZamq8a46M-lB5jurEbN1mfpj3GvdSYKzdVCdSFfLqP76eCI1pblinW4b-6w-oVdn0JJ1icHPpkxVmJW-2Hok69iHcqrBtRO9AZpTsTEvKekeI4mIyhYGLi9AzzQyhV0c3GImTXFoutng5t7GyzBUoRpI0W4YeQzYa6TEzGRTylIfGPemATF_OReENp0TlLbb3gsHw",
"qi": "m7uZk4AsOfJ1V2RY8lmEF518toCV7juKuS_b_OUx8B0dRG0_kbF1cH-Tmrgsya3bwkYx5HeZG81rX7SRjh-0nVPOMW3tGqU5U9f59DXqvOItJIJ6wvWvWXnuna2-NstYCotFQWadIKjk4wjEKj-a4NJt4D_F4csyeyqWOH2DiUFzBGGxxdEoD5t_HEeNXuWQ6-SiV0x5ZVMln3TSh7IOMl70Smm8HcQF5mOsWg3N0wIg-yffxPrs6r15TRuW1MfT-bZk2GLrtHF1TkIoT1e00jWK4eBl2oRxiJGONUBMTEHV85Fr0yztnA99AgHnrMbE_4ehvev4h5DEWvFyFuJN_g"
}`,
},
{
name: "ed25519",
in: `{
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8",
"d": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU"
}`,
},
{
name: "p-256",
in: `{
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM",
"d": "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc"
}`,
},
{
name: "p-384",
in: `{
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv",
"d": "hAyGZNj9031guBCdpAOaZkO-E5m-LKLYnMIq0-msrp8JLctseaOeNTHmP3uKVWwX"
}`,
},
{
name: "p-521",
in: `{
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC",
"d": "AHwRaNaGs0jkj_pT6PK2aHep7dJK-yxyoL2bIfVRAceq1baxoiFDo3W14c8E2YZn1k5S53r4a11flhQdaB5guJ_X"
}`,
},
{
name: "secp256k1",
in: `{
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc",
"d": "J5yKm7OXFsXDEutteGYeT0CAfQJwIlHLSYkQxKtgiyo"
}`,
},
{
name: "x25519",
in: `{
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs",
"d": "aEAAB3VBFPCQtgF3N__wRiXhMOgeiRGstpPC3gnJ1Eo"
}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
var priv PrivateJwk
err := json.Unmarshal([]byte(tc.in), &priv)
require.NoError(t, err)
bytes, err := json.Marshal(priv)
require.NoError(t, err)
require.JSONEq(t, tc.in, string(bytes))
})
}
}

179
crypto/jwk/public.go Normal file
View File

@@ -0,0 +1,179 @@
package jwk
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/ed25519"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/p384"
"github.com/INFURA/go-did/crypto/p521"
"github.com/INFURA/go-did/crypto/rsa"
"github.com/INFURA/go-did/crypto/secp256k1"
"github.com/INFURA/go-did/crypto/x25519"
)
// Specification:
// - https://www.rfc-editor.org/rfc/rfc7517#section-4 (JWK)
// - https://www.iana.org/assignments/jose/jose.xhtml#web-key-types (key parameters)
type PublicJwk struct {
Pubkey crypto.PublicKey
}
func (pj PublicJwk) MarshalJSON() ([]byte, error) {
switch pubkey := pj.Pubkey.(type) {
case ed25519.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
}{
Kty: "OKP",
Crv: "Ed25519",
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
})
case *p256.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}{
Kty: "EC",
Crv: "P-256",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
})
case *p384.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}{
Kty: "EC",
Crv: "P-384",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
})
case *p521.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}{
Kty: "EC",
Crv: "P-521",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
})
case *rsa.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
N string `json:"n"`
E string `json:"e"`
}{
Kty: "RSA",
N: base64.RawURLEncoding.EncodeToString(pubkey.NBytes()),
E: base64.RawURLEncoding.EncodeToString(pubkey.EBytes()),
})
case *secp256k1.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
Y string `json:"y"`
}{
Kty: "EC",
Crv: "secp256k1",
X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()),
Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()),
})
case *x25519.PublicKey:
return json.Marshal(struct {
Kty string `json:"kty"`
Crv string `json:"crv"`
X string `json:"x"`
}{
Kty: "OKP",
Crv: "X25519",
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
})
default:
return nil, fmt.Errorf("unsupported key type %T", pubkey)
}
}
func (pj *PublicJwk) UnmarshalJSON(bytes []byte) error {
aux := make(map[string]string)
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
switch aux["kty"] {
case "EC": // Elliptic curve
x, err := base64.RawURLEncoding.DecodeString(aux["x"])
if err != nil {
return fmt.Errorf("invalid x parameter with kty=EC: %w", err)
}
y, err := base64.RawURLEncoding.DecodeString(aux["y"])
if err != nil {
return fmt.Errorf("invalid y parameter with kty=EC: %w", err)
}
switch aux["crv"] {
case "P-256":
pj.Pubkey, err = p256.PublicKeyFromXY(x, y)
return err
case "P-384":
pj.Pubkey, err = p384.PublicKeyFromXY(x, y)
return err
case "P-521":
pj.Pubkey, err = p521.PublicKeyFromXY(x, y)
return err
case "secp256k1":
pj.Pubkey, err = secp256k1.PublicKeyFromXY(x, y)
return err
default:
return fmt.Errorf("unsupported Curve %s", aux["crv"])
}
case "RSA":
n, err := base64.RawURLEncoding.DecodeString(aux["n"])
if err != nil {
return fmt.Errorf("invalid n parameter with kty=RSA: %w", err)
}
e, err := base64.RawURLEncoding.DecodeString(aux["e"])
if err != nil {
return fmt.Errorf("invalid e parameter with kty=RSA: %w", err)
}
pj.Pubkey, err = rsa.PublicKeyFromNE(n, e)
return err
case "OKP": // Octet key pair
x, err := base64.RawURLEncoding.DecodeString(aux["x"])
if err != nil {
return fmt.Errorf("invalid x parameter with kty=OKP: %w", err)
}
switch aux["crv"] {
case "Ed25519":
pj.Pubkey, err = ed25519.PublicKeyFromBytes(x)
return err
case "X25519":
pj.Pubkey, err = x25519.PublicKeyFromBytes(x)
return err
default:
return fmt.Errorf("unsupported Curve %s", aux["crv"])
}
default:
return fmt.Errorf("unsupported key type %s", aux["kty"])
}
}

88
crypto/jwk/public_test.go Normal file
View File

@@ -0,0 +1,88 @@
package jwk
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
// Origin: https://github.com/w3c-ccg/did-key-spec/tree/main/test-vectors
func TestPublicJwkRoundtrip(t *testing.T) {
for _, tc := range []struct {
name string
in string
}{
{
name: "RSA",
in: `{
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB"
}`,
},
{
name: "ed25519",
in: `{
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8"
}`,
},
{
name: "p-256",
in: `{
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM"
}`,
},
{
name: "p-384",
in: `{
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv"
}`,
},
{
name: "p-521",
in: `{
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC"
}`,
},
{
name: "secp256k1",
in: `{
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc"
}`,
},
{
name: "x25519",
in: `{
"kty": "OKP",
"crv": "X25519",
"x": "467ap28wHJGEXJAb4mLrokqq8A-txA_KmoQTcj31XzU"
}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
var pub PublicJwk
err := json.Unmarshal([]byte(tc.in), &pub)
require.NoError(t, err)
bytes, err := json.Marshal(pub)
require.NoError(t, err)
require.JSONEq(t, tc.in, string(bytes))
})
}
}

29
crypto/options.go Normal file
View File

@@ -0,0 +1,29 @@
package crypto
type SigningOpts struct {
Hash Hash
}
func CollectSigningOptions(opts []SigningOption) SigningOpts {
res := SigningOpts{}
for _, opt := range opts {
opt(&res)
}
return res
}
func (opts SigningOpts) HashOrDefault(_default Hash) Hash {
if opts.Hash == 0 {
return _default
}
return opts.Hash
}
type SigningOption func(opts *SigningOpts)
// WithSigningHash specify the hash algorithm to be used for signatures
func WithSigningHash(hash Hash) SigningOption {
return func(opts *SigningOpts) {
opts.Hash = hash
}
}

View File

@@ -8,13 +8,16 @@ import (
const (
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
PublicKeyBytesSize = 33
PublicKeyBytesSize = 1 + coordinateSize
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
PrivateKeyBytesSize = 32
PrivateKeyBytesSize = coordinateSize
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
SignatureBytesSize = 64
SignatureBytesSize = 2 * coordinateSize
MultibaseCode = uint64(0x1200)
// coordinateSize is the size, in bytes, of one coordinate in the elliptic curve.
coordinateSize = 32
)
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
@@ -23,7 +26,7 @@ func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
return nil, nil, err
}
pub := priv.Public().(*ecdsa.PublicKey)
return (*PublicKey)(pub), (*PrivateKey)(priv), nil
return &PublicKey{k: pub}, &PrivateKey{k: priv}, nil
}
const (

View File

@@ -1,8 +1,12 @@
package p256
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/require"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
@@ -17,6 +21,8 @@ var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA256,
OtherHashes: []crypto.Hash{crypto.SHA224, crypto.SHA384, crypto.SHA512},
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,
@@ -29,3 +35,25 @@ func TestSuite(t *testing.T) {
func BenchmarkSuite(b *testing.B) {
testsuite.BenchSuite(b, harness)
}
func TestSignatureASN1(t *testing.T) {
// openssl ecparam -genkey -name prime256v1 -noout -out private.pem
// openssl ec -in private.pem -pubout -out public.pem
// echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der
// echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der
pubPem := `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+UhEHZqcaKn+qhNtMmW843ZTRkX/
6GzxOWoRD2nv3EewARM90akj2UAKwQjJR9ibm78XtdlryvWG1v8TWb8INA==
-----END PUBLIC KEY-----
`
pub, err := PublicKeyFromX509PEM(pubPem)
require.NoError(t, err)
b64sig := `MEQCIHPslthrLAYgwfqYaUmtGJqwmH7sRf5FEnnKgzcHIF8fAiB9+qovdvN6yJKkBwoQCw798uWr
0nOUE55ftB8EgX/Jbg==`
sig, err := base64.StdEncoding.DecodeString(b64sig)
require.NoError(t, err)
require.True(t, pub.VerifyASN1([]byte("message"), sig))
}

View File

@@ -4,7 +4,6 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
@@ -13,10 +12,14 @@ import (
"github.com/INFURA/go-did/crypto"
)
var _ crypto.SigningPrivateKey = (*PrivateKey)(nil)
var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil)
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
var _ crypto.PrivateKeyToBytes = &PrivateKey{}
var _ crypto.PrivateKeyKeyExchange = &PrivateKey{}
type PrivateKey ecdsa.PrivateKey
type PrivateKey struct {
k *ecdsa.PrivateKey
}
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
@@ -26,15 +29,17 @@ func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
return nil, fmt.Errorf("invalid P-256 private key size")
}
res := &ecdsa.PrivateKey{
D: new(big.Int).SetBytes(b),
PublicKey: ecdsa.PublicKey{Curve: elliptic.P256()},
res := &PrivateKey{
k: &ecdsa.PrivateKey{
D: new(big.Int).SetBytes(b),
PublicKey: ecdsa.PublicKey{Curve: elliptic.P256()},
},
}
// recompute the public key
res.PublicKey.X, res.PublicKey.Y = res.PublicKey.Curve.ScalarBaseMult(b)
res.k.PublicKey.X, res.k.PublicKey.Y = res.k.PublicKey.Curve.ScalarBaseMult(b)
return (*PrivateKey)(res), nil
return res, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
@@ -43,8 +48,11 @@ func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
if err != nil {
return nil, err
}
ecdsaPriv := priv.(*ecdsa.PrivateKey)
return (*PrivateKey)(ecdsaPriv), nil
ecdsaPriv, ok := priv.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid private key type")
}
return &PrivateKey{k: ecdsaPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
@@ -61,25 +69,25 @@ func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return (*ecdsa.PrivateKey)(p).Equal((*ecdsa.PrivateKey)(other))
return p.k.Equal(other.k)
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
ecdhPub := (*ecdsa.PrivateKey)(p).Public().(*ecdsa.PublicKey)
return (*PublicKey)(ecdhPub)
ecdhPub := p.k.Public().(*ecdsa.PublicKey)
return &PublicKey{k: ecdhPub}
}
func (p *PrivateKey) ToBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [PrivateKeyBytesSize]byte
((*ecdsa.PrivateKey)(p)).D.FillBytes(buf[:])
(p.k).D.FillBytes(buf[:])
return buf[:]
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey((*ecdsa.PrivateKey)(p))
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
return res
}
@@ -91,33 +99,35 @@ func (p *PrivateKey) ToPKCS8PEM() string {
}))
}
/*
Note: signatures for the crypto.SigningPrivateKey interface assumes SHA256,
which should be correct almost always. If there is a need to use a different
hash function, we can add separate functions that have that flexibility.
*/
// The default signing hash is SHA-256.
func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
func (p *PrivateKey) SignToBytes(message []byte) ([]byte, error) {
// Hash the message with SHA-256
hash := sha256.Sum256(message)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
r, s, err := ecdsa.Sign(rand.Reader, (*ecdsa.PrivateKey)(p), hash[:])
r, s, err := ecdsa.Sign(rand.Reader, p.k, hash[:])
if err != nil {
return nil, err
}
sig := make([]byte, 64)
r.FillBytes(sig[:32])
s.FillBytes(sig[32:])
sig := make([]byte, SignatureBytesSize)
r.FillBytes(sig[:SignatureBytesSize/2])
s.FillBytes(sig[SignatureBytesSize/2:])
return sig, nil
}
func (p *PrivateKey) SignToASN1(message []byte) ([]byte, error) {
// Hash the message with SHA-256
hash := sha256.Sum256(message)
// The default signing hash is SHA-256.
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
return ecdsa.SignASN1(rand.Reader, (*ecdsa.PrivateKey)(p), hash[:])
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.SignASN1(rand.Reader, p.k, hash[:])
}
func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
@@ -130,11 +140,11 @@ func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
if remote, ok := remote.(*PublicKey); ok {
// First, we need to convert the ECDSA (signing only) to the equivalent ECDH keys
ecdhPriv, err := (*ecdsa.PrivateKey)(p).ECDH()
ecdhPriv, err := p.k.ECDH()
if err != nil {
return nil, err
}
ecdhPub, err := (*ecdsa.PublicKey)(remote).ECDH()
ecdhPub, err := remote.k.ECDH()
if err != nil {
return nil, err
}

View File

@@ -3,19 +3,22 @@ package p256
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/_helpers"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.SigningPublicKey = (*PublicKey)(nil)
var _ crypto.PublicKeySigningBytes = &PublicKey{}
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
var _ crypto.PublicKeyToBytes = &PublicKey{}
type PublicKey ecdsa.PublicKey
type PublicKey struct {
k *ecdsa.PublicKey
}
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
@@ -28,7 +31,21 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if x == nil {
return nil, fmt.Errorf("invalid P-256 public key")
}
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}), nil
return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}}, nil
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
pub := &PublicKey{k: &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: new(big.Int).SetBytes(x),
Y: new(big.Int).SetBytes(y),
}}
if !elliptic.P256().IsOnCurve(pub.k.X, pub.k.Y) {
return nil, fmt.Errorf("invalid P-256 public key")
}
return pub, nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
@@ -49,8 +66,11 @@ func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
if err != nil {
return nil, err
}
ecdsaPub := pub.(*ecdsa.PublicKey)
return (*PublicKey)(ecdsaPub), nil
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key")
}
return &PublicKey{k: ecdsaPub}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
@@ -65,26 +85,38 @@ func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) XBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).X.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) YBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).Y.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return (*ecdsa.PublicKey)(p).Equal((*ecdsa.PublicKey)(other))
return p.k.Equal(other.k)
}
return false
}
func (p *PublicKey) ToBytes() []byte {
ecdsaPub := (*ecdsa.PublicKey)(p)
return elliptic.MarshalCompressed(elliptic.P256(), ecdsaPub.X, ecdsaPub.Y)
return elliptic.MarshalCompressed(elliptic.P256(), p.k.X, p.k.Y)
}
func (p *PublicKey) ToPublicKeyMultibase() string {
ecdsaPub := (*ecdsa.PublicKey)(p)
bytes := elliptic.MarshalCompressed(elliptic.P256(), ecdsaPub.X, ecdsaPub.Y)
bytes := elliptic.MarshalCompressed(elliptic.P256(), p.k.X, p.k.Y)
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey((*ecdsa.PublicKey)(p))
res, _ := x509.MarshalPKIXPublicKey(p.k)
return res
}
@@ -96,29 +128,29 @@ func (p *PublicKey) ToX509PEM() string {
}))
}
/*
Note: signatures for the crypto.SigningPrivateKey interface assumes SHA256,
which should be correct almost always. If there is a need to use a different
hash function, we can add separate functions that have that flexibility.
*/
func (p *PublicKey) VerifyBytes(message, signature []byte) bool {
// The default signing hash is SHA-256.
func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
if len(signature) != SignatureBytesSize {
return false
}
// Hash the message with SHA-256
hash := sha256.Sum256(message)
// For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it.
// This means it's actually more efficient to encode the signature as ASN.1 here.
sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:])
if err != nil {
return false
}
r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:])
return ecdsa.Verify((*ecdsa.PublicKey)(p), hash[:], r, s)
return p.VerifyASN1(message, sigAsn1, opts...)
}
func (p *PublicKey) VerifyASN1(message, signature []byte) bool {
// Hash the message with SHA-256
hash := sha256.Sum256(message)
// The default signing hash is SHA-256.
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
return ecdsa.VerifyASN1((*ecdsa.PublicKey)(p), hash[:], signature)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.VerifyASN1(p.k, hash[:], signature)
}

35
crypto/p384/key.go Normal file
View File

@@ -0,0 +1,35 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
)
const (
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
PublicKeyBytesSize = 1 + coordinateSize
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
PrivateKeyBytesSize = coordinateSize
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
SignatureBytesSize = 2 * coordinateSize
MultibaseCode = uint64(0x1201)
// coordinateSize is the size, in bytes, of one coordinate in the elliptic curve.
coordinateSize = 48
)
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, nil, err
}
pub := priv.Public().(*ecdsa.PublicKey)
return &PublicKey{k: pub}, &PrivateKey{k: priv}, nil
}
const (
pemPubBlockType = "PUBLIC KEY"
pemPrivBlockType = "PRIVATE KEY"
)

34
crypto/p384/key_test.go Normal file
View File

@@ -0,0 +1,34 @@
package p384
import (
"testing"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "p384",
GenerateKeyPair: GenerateKeyPair,
PublicKeyFromBytes: PublicKeyFromBytes,
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromBytes: PrivateKeyFromBytes,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA384,
OtherHashes: []crypto.Hash{crypto.SHA256, crypto.SHA512},
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,
}
func TestSuite(t *testing.T) {
testsuite.TestSuite(t, harness)
}
func BenchmarkSuite(b *testing.B) {
testsuite.BenchSuite(b, harness)
}

155
crypto/p384/private.go Normal file
View File

@@ -0,0 +1,155 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
)
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
var _ crypto.PrivateKeyToBytes = &PrivateKey{}
var _ crypto.PrivateKeyKeyExchange = &PrivateKey{}
type PrivateKey struct {
k *ecdsa.PrivateKey
}
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if len(b) != PrivateKeyBytesSize {
return nil, fmt.Errorf("invalid P-384 private key size")
}
res := &PrivateKey{
k: &ecdsa.PrivateKey{
D: new(big.Int).SetBytes(b),
PublicKey: ecdsa.PublicKey{Curve: elliptic.P384()},
},
}
// recompute the public key
res.k.PublicKey.X, res.k.PublicKey.Y = res.k.PublicKey.Curve.ScalarBaseMult(b)
return res, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
priv, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
ecdsaPriv, ok := priv.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid private key type")
}
return &PrivateKey{k: ecdsaPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPrivBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PrivateKeyFromPKCS8DER(block.Bytes)
}
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
ecdhPub := p.k.Public().(*ecdsa.PublicKey)
return &PublicKey{k: ecdhPub}
}
func (p *PrivateKey) ToBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [PrivateKeyBytesSize]byte
(p.k).D.FillBytes(buf[:])
return buf[:]
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
return res
}
func (p *PrivateKey) ToPKCS8PEM() string {
der := p.ToPKCS8DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPrivBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-384.
func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA384).New()
hasher.Write(message)
hash := hasher.Sum(nil)
r, s, err := ecdsa.Sign(rand.Reader, p.k, hash[:])
if err != nil {
return nil, err
}
sig := make([]byte, SignatureBytesSize)
r.FillBytes(sig[:SignatureBytesSize/2])
s.FillBytes(sig[SignatureBytesSize/2:])
return sig, nil
}
// The default signing hash is SHA-384.
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA384).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.SignASN1(rand.Reader, p.k, hash[:])
}
func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
if _, ok := remote.(*PublicKey); ok {
return true
}
return false
}
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
if remote, ok := remote.(*PublicKey); ok {
// First, we need to convert the ECDSA (signing only) to the equivalent ECDH keys
ecdhPriv, err := p.k.ECDH()
if err != nil {
return nil, err
}
ecdhPub, err := remote.k.ECDH()
if err != nil {
return nil, err
}
return ecdhPriv.ECDH(ecdhPub)
}
return nil, fmt.Errorf("incompatible public key")
}

156
crypto/p384/public.go Normal file
View File

@@ -0,0 +1,156 @@
package p384
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKeySigningBytes = &PublicKey{}
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
var _ crypto.PublicKeyToBytes = &PublicKey{}
type PublicKey struct {
k *ecdsa.PublicKey
}
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != PublicKeyBytesSize {
return nil, fmt.Errorf("invalid P-384 public key size")
}
x, y := elliptic.UnmarshalCompressed(elliptic.P384(), b)
if x == nil {
return nil, fmt.Errorf("invalid P-384 public key")
}
return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}}, nil
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
pub := &PublicKey{k: &ecdsa.PublicKey{
Curve: elliptic.P384(),
X: new(big.Int).SetBytes(x),
Y: new(big.Int).SetBytes(y),
}}
if !elliptic.P384().IsOnCurve(pub.k.X, pub.k.Y) {
return nil, fmt.Errorf("invalid P-384 public key")
}
return pub, nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromBytes(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(bytes)
if err != nil {
return nil, err
}
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key")
}
return &PublicKey{k: ecdsaPub}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) XBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).X.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) YBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).Y.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PublicKey) ToBytes() []byte {
return elliptic.MarshalCompressed(elliptic.P384(), p.k.X, p.k.Y)
}
func (p *PublicKey) ToPublicKeyMultibase() string {
bytes := elliptic.MarshalCompressed(elliptic.P384(), p.k.X, p.k.Y)
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey(p.k)
return res
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-384.
func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
if len(signature) != SignatureBytesSize {
return false
}
// For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it.
// This means it's actually more efficient to encode the signature as ASN.1 here.
sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:])
if err != nil {
return false
}
return p.VerifyASN1(message, sigAsn1, opts...)
}
// The default signing hash is SHA-384.
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA384).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.VerifyASN1(p.k, hash[:], signature)
}

35
crypto/p521/key.go Normal file
View File

@@ -0,0 +1,35 @@
package p521
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
)
const (
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
PublicKeyBytesSize = 1 + coordinateSize
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
PrivateKeyBytesSize = coordinateSize
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
SignatureBytesSize = 2 * coordinateSize
MultibaseCode = uint64(0x1202)
// coordinateSize is the size, in bytes, of one coordinate in the elliptic curve.
coordinateSize = 66
)
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, err
}
pub := priv.Public().(*ecdsa.PublicKey)
return &PublicKey{k: pub}, &PrivateKey{k: priv}, nil
}
const (
pemPubBlockType = "PUBLIC KEY"
pemPrivBlockType = "PRIVATE KEY"
)

34
crypto/p521/key_test.go Normal file
View File

@@ -0,0 +1,34 @@
package p521
import (
"testing"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "p521",
GenerateKeyPair: GenerateKeyPair,
PublicKeyFromBytes: PublicKeyFromBytes,
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromBytes: PrivateKeyFromBytes,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA512,
OtherHashes: []crypto.Hash{crypto.SHA384},
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,
}
func TestSuite(t *testing.T) {
testsuite.TestSuite(t, harness)
}
func BenchmarkSuite(b *testing.B) {
testsuite.BenchSuite(b, harness)
}

155
crypto/p521/private.go Normal file
View File

@@ -0,0 +1,155 @@
package p521
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
)
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
var _ crypto.PrivateKeyToBytes = &PrivateKey{}
var _ crypto.PrivateKeyKeyExchange = &PrivateKey{}
type PrivateKey struct {
k *ecdsa.PrivateKey
}
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if len(b) != PrivateKeyBytesSize {
return nil, fmt.Errorf("invalid P-521 private key size")
}
res := &PrivateKey{
k: &ecdsa.PrivateKey{
D: new(big.Int).SetBytes(b),
PublicKey: ecdsa.PublicKey{Curve: elliptic.P521()},
},
}
// recompute the public key
res.k.PublicKey.X, res.k.PublicKey.Y = res.k.PublicKey.Curve.ScalarBaseMult(b)
return res, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
priv, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
ecdsaPriv, ok := priv.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid private key type")
}
return &PrivateKey{k: ecdsaPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPrivBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PrivateKeyFromPKCS8DER(block.Bytes)
}
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
ecdhPub := p.k.Public().(*ecdsa.PublicKey)
return &PublicKey{k: ecdhPub}
}
func (p *PrivateKey) ToBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [PrivateKeyBytesSize]byte
(p.k).D.FillBytes(buf[:])
return buf[:]
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
return res
}
func (p *PrivateKey) ToPKCS8PEM() string {
der := p.ToPKCS8DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPrivBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-512.
func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA512).New()
hasher.Write(message)
hash := hasher.Sum(nil)
r, s, err := ecdsa.Sign(rand.Reader, p.k, hash[:])
if err != nil {
return nil, err
}
sig := make([]byte, SignatureBytesSize)
r.FillBytes(sig[:SignatureBytesSize/2])
s.FillBytes(sig[SignatureBytesSize/2:])
return sig, nil
}
// The default signing hash is SHA-512.
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA512).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.SignASN1(rand.Reader, p.k, hash[:])
}
func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
if _, ok := remote.(*PublicKey); ok {
return true
}
return false
}
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
if remote, ok := remote.(*PublicKey); ok {
// First, we need to convert the ECDSA (signing only) to the equivalent ECDH keys
ecdhPriv, err := p.k.ECDH()
if err != nil {
return nil, err
}
ecdhPub, err := remote.k.ECDH()
if err != nil {
return nil, err
}
return ecdhPriv.ECDH(ecdhPub)
}
return nil, fmt.Errorf("incompatible public key")
}

156
crypto/p521/public.go Normal file
View File

@@ -0,0 +1,156 @@
package p521
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKeySigningBytes = &PublicKey{}
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
var _ crypto.PublicKeyToBytes = &PublicKey{}
type PublicKey struct {
k *ecdsa.PublicKey
}
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if len(b) != PublicKeyBytesSize {
return nil, fmt.Errorf("invalid P-521 public key size")
}
x, y := elliptic.UnmarshalCompressed(elliptic.P521(), b)
if x == nil {
return nil, fmt.Errorf("invalid P-521 public key")
}
return &PublicKey{k: &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}}, nil
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
pub := &PublicKey{k: &ecdsa.PublicKey{
Curve: elliptic.P521(),
X: new(big.Int).SetBytes(x),
Y: new(big.Int).SetBytes(y),
}}
if !elliptic.P521().IsOnCurve(pub.k.X, pub.k.Y) {
return nil, fmt.Errorf("invalid P-521 public key")
}
return pub, nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromBytes(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(bytes)
if err != nil {
return nil, err
}
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key")
}
return &PublicKey{k: ecdsaPub}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) XBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).X.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) YBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
(p.k).Y.FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PublicKey) ToBytes() []byte {
return elliptic.MarshalCompressed(elliptic.P521(), p.k.X, p.k.Y)
}
func (p *PublicKey) ToPublicKeyMultibase() string {
bytes := elliptic.MarshalCompressed(elliptic.P521(), p.k.X, p.k.Y)
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey(p.k)
return res
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-512.
func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
if len(signature) != SignatureBytesSize {
return false
}
// For some reason, the go crypto library in ecdsa.Verify() encodes the signature as ASN.1 to then decode it.
// This means it's actually more efficient to encode the signature as ASN.1 here.
sigAsn1, err := helpers.EncodeSignatureToASN1(signature[:SignatureBytesSize/2], signature[SignatureBytesSize/2:])
if err != nil {
return false
}
return p.VerifyASN1(message, sigAsn1, opts...)
}
// The default signing hash is SHA-512.
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA512).New()
hasher.Write(message)
hash := hasher.Sum(nil)
return ecdsa.VerifyASN1(p.k, hash[:], signature)
}

43
crypto/rsa/key.go Normal file
View File

@@ -0,0 +1,43 @@
package rsa
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"github.com/INFURA/go-did/crypto"
)
const (
MultibaseCode = uint64(0x1205)
MinRsaKeyBits = 2048
MaxRsaKeyBits = 8192
)
func GenerateKeyPair(bits int) (*PublicKey, *PrivateKey, error) {
if bits < MinRsaKeyBits || bits > MaxRsaKeyBits {
return nil, nil, fmt.Errorf("invalid key size: %d", bits)
}
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, nil, err
}
return &PublicKey{k: &priv.PublicKey}, &PrivateKey{k: priv}, nil
}
const (
pemPubBlockType = "PUBLIC KEY"
pemPrivBlockType = "PRIVATE KEY"
)
func defaultSigHash(keyLen int) crypto.Hash {
switch {
case keyLen <= 2048:
return crypto.SHA256
case keyLen <= 3072:
return crypto.SHA384
default:
return crypto.SHA512
}
}

164
crypto/rsa/key_test.go Normal file
View File

@@ -0,0 +1,164 @@
package rsa
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/require"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
var harness2048 = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "rsa-2048",
GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(2048) },
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA256,
OtherHashes: []crypto.Hash{crypto.SHA384, crypto.SHA512},
}
var harness3072 = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "rsa-3072",
GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(3072) },
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA384,
OtherHashes: []crypto.Hash{crypto.SHA512},
}
var harness4096 = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "rsa-4096",
GenerateKeyPair: func() (*PublicKey, *PrivateKey, error) { return GenerateKeyPair(4096) },
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA512,
OtherHashes: []crypto.Hash{},
}
func TestSuite2048(t *testing.T) {
testsuite.TestSuite(t, harness2048)
}
func TestSuite3072(t *testing.T) {
testsuite.TestSuite(t, harness3072)
}
func TestSuite4096(t *testing.T) {
testsuite.TestSuite(t, harness4096)
}
func BenchmarkSuite2048(b *testing.B) {
testsuite.BenchSuite(b, harness2048)
}
func BenchmarkSuite3072(b *testing.B) {
testsuite.BenchSuite(b, harness3072)
}
func BenchmarkSuite4096(b *testing.B) {
testsuite.BenchSuite(b, harness4096)
}
func TestPublicKeyX509(t *testing.T) {
// openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
// openssl pkey -in private_key.pem -pubout -out public_key.pem
pem := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyLFQUbVVo/rctJaCzR5z
g622eUNBwZmA1vnDEXnHWBl3y5RJF5zyTdlouujjmEuu6qsXk1NCNQ3dLH2iquI8
iFFAhS4kTX6JS+wR3vHLhga1oFkPceGFEUG/3vxn52ozFs8hikhq/P09HmLub7Vc
VklwrGvTbEa5Fn/2Kz6olw5ExYI14Unsl+A3iw8AXPL9/acD+ehoyx3/zKFrVTKx
e9jdoWX8L7IpqM2HOSu23/3E2IwH2GdY0C8575AiD/O555hie7JHkzF3I4E85gPd
ZgXYFShIfgOzDV0q4oP0pzqYkErhdjOpigCMjDuIC4OueZYqYJrP2rdpzuqoqk07
NwIDAQAB
-----END PUBLIC KEY-----
`
pub, err := PublicKeyFromX509PEM(pem)
require.NoError(t, err)
rt := pub.ToX509PEM()
require.Equal(t, pem, rt)
}
func TestPrivateKeyPKCS8(t *testing.T) {
// openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
pem := `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDIsVBRtVWj+ty0
loLNHnODrbZ5Q0HBmYDW+cMRecdYGXfLlEkXnPJN2Wi66OOYS67qqxeTU0I1Dd0s
faKq4jyIUUCFLiRNfolL7BHe8cuGBrWgWQ9x4YURQb/e/GfnajMWzyGKSGr8/T0e
Yu5vtVxWSXCsa9NsRrkWf/YrPqiXDkTFgjXhSeyX4DeLDwBc8v39pwP56GjLHf/M
oWtVMrF72N2hZfwvsimozYc5K7bf/cTYjAfYZ1jQLznvkCIP87nnmGJ7skeTMXcj
gTzmA91mBdgVKEh+A7MNXSrig/SnOpiQSuF2M6mKAIyMO4gLg655lipgms/at2nO
6qiqTTs3AgMBAAECggEAVFVqZoN4QumSYBKVUYOX0AAp2ygflC6gnPWkeo39bjB5
jiM4WcNacMtIvq5JoYBANx2BUSfd/PRf+ierOPrLrA7UuYJLwALJyA0h71kVCLN+
FC0Il/bIF5nU+mt/cBfI8y9ELVtEFh6GVeQFxQxlil7fCZ1f4TKQ6XsJI1/3sU2P
hbOuyfKKiWym8n5BV6NP3gotjnT01I+seplx3oMOKIaGl0KMgkuU2r8o8WMjA7Gx
1WWPJDpUdyYDYSUH8PubXowHkE+2RXddZ+tGvS8mF/A4Q0hdj2T9XvzyZ813O9Tv
n522A9QQE8YlqwAYh4z3VoNhz+Fi1mQfYsIblNygSQKBgQDrk+kB/dz92RPhP/rh
zAOvwRuI2TOaw98kdgpVlb6gMVmN2EWkzkdnwQDJhV+MFZob4wi+TpsDPv4fjubq
gqbM/MYc0kNtIEA4GkIJLCK5Hh7c6kCQfya+/eq4Ju6C3+I4R46/+9E7ixA83Zjf
ftqTlYOrlMby84Lvsf81LtiMiQKBgQDaFzXpDBPOIaup68k9NeZyXHKI8wNQXkui
JyjM9A3U2D8O9Yty8G+Oq0B4oUGlyenMGJiQmf3bAffJBkLCMXCGXYD8CCKsiSJ6
R6XBfbpPkzCwl67FFN/8Z0nxZ0lbxd2ZMTC4qxH4peD5TNZM89kTpSNXPrr55zzm
qREmxisZvwKBgQCNK3jBScjpkfFY1UdZkjFPXDBM5KQJBYGtztLIkNDIHGqnFsg9
R6QAp+b53GPyhWtxdK7jpCU+X7xXWwJD3AFq67sowFPJjD8Pn6Sc7IbuWf9ysSn5
rUihwXWr3yCk6tcclL0VjSjIPsB/SOf4XoNLV5is9J34Lzbyvr7JtwXryQKBgQCM
m3xRdUzrkD/J/M+w3ChoQPxDGVJgpXrj35Vplku4l3cIYPz4LNXvyK93VpgpmGVZ
Bd6PFAlcAwfLHnM6Gn/u0SgQ1fns/TkyVzEh77qIBWDV6eVvAQdsBvfgYPQl7Arz
8ofz969NfTzv3j8oO+sPxF9lp3cLGa/lEsmREyDEpwKBgQCvW+NK93oajo358gKh
/xfSv7yMiSL26NcIgHmQouZVXJ3Dg0KSISx8tgY0/7TwC2mPa0Ryhpb/3HtAIXoY
eqkQGHqnC4voxSoati667mMGdHL1+12WvQmhfTLCWmZ5ccNlR+aFD20TGbMxnejS
XnARctVkIcUYORcYwvuu9meDkw==
-----END PRIVATE KEY-----
`
priv, err := PrivateKeyFromPKCS8PEM(pem)
require.NoError(t, err)
rt := priv.ToPKCS8PEM()
require.Equal(t, pem, rt)
}
func TestSignatureASN1(t *testing.T) {
// openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
// openssl pkey -in private.pem -pubout -out public.pem
// echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der
// echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der
pubPem := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmtKXCTkUDcKbZGsEEUTo
16xblCyh6zmA8pXVGgmC66QiTNdKzxdwTykXTDs9sEre9ea34h2M7dwrA1weAmBu
grAXe0QmIXIqjFKRKdfty09yWVtKF7FGwEMlhKftWC225R+tRuLwbKG4cCSzHxcf
JfqCYqGDM7BrF39ilQzFYw5sUiWn3ppRPWa2oEV3cw19zFnHMbEHIQIdFyCcIv5x
GUSJ6sJVp0YvsODsZbA+Zyb2UMRfXD8fDHm9bJQCY0x/wGJLfvJmWtZLciwc145U
BN3SezY30NviZtZBKWjXgb6gL69L94U10/8ghmA30DY7bKs4+/7R2nOw91CO4rCo
1QIDAQAB
-----END PUBLIC KEY-----
`
pub, err := PublicKeyFromX509PEM(pubPem)
require.NoError(t, err)
b64sig := `BdvBkZWxIVE2mfM48H1WlOs3k9NzyS4oUxAMOZWNNTYDU6+DLbhZ7Hnt3rRKX3m6f1cX5DCsHcPC
6sNtsR8Xp9u09GWCN/K28fF7Pcl0E87MdhAUL7jKNK5bb1XWx/GCUmoKXRZiR/gA10iB2Lmjd1MC
HItTCig91gmFm5PO67u9yM+cqE2nGyOh13/kT5Np9MUyaE9dkjoQGum23Ta6m7v0atWsPhO5aVVI
76vLwGhYAhQe22RxBlPRXyRInr0EnVgHQOe211o//erPZYQAm+N1kK+yjV8NbPxJX+r5sYUE19NL
MCB+kOgWk51uJwuiuHlffGMBPxku/t+skxI7Bw==`
sig, err := base64.StdEncoding.DecodeString(b64sig)
require.NoError(t, err)
require.True(t, pub.VerifyASN1([]byte("message"), sig))
}

164
crypto/rsa/private.go Normal file
View File

@@ -0,0 +1,164 @@
package rsa
import (
stdcrypto "crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
)
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
type PrivateKey struct {
k *rsa.PrivateKey
}
func PrivateKeyFromNEDPQ(n, e, d, p, q []byte) (*PrivateKey, error) {
pub, err := PublicKeyFromNE(n, e)
if err != nil {
return nil, err
}
dBInt := new(big.Int).SetBytes(d)
pBInt := new(big.Int).SetBytes(p)
qBInt := new(big.Int).SetBytes(q)
priv := &rsa.PrivateKey{
PublicKey: *pub.k,
D: dBInt,
Primes: []*big.Int{pBInt, qBInt},
}
err = priv.Validate()
if err != nil {
return nil, err
}
priv.Precompute()
return &PrivateKey{k: priv}, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
priv, err := x509.ParsePKCS8PrivateKey(bytes)
if err != nil {
return nil, err
}
rsaPriv, ok := priv.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid private key type")
}
return &PrivateKey{k: rsaPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPrivBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PrivateKeyFromPKCS8DER(block.Bytes)
}
func (p *PrivateKey) BitLen() int {
return p.k.N.BitLen()
}
func (p *PrivateKey) DBytes() []byte {
byteLength := (p.k.D.BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.D.FillBytes(buf)
return buf
}
func (p *PrivateKey) PBytes() []byte {
byteLength := (p.k.Primes[0].BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.Primes[0].FillBytes(buf)
return buf
}
func (p *PrivateKey) QBytes() []byte {
byteLength := (p.k.Primes[1].BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.Primes[1].FillBytes(buf)
return buf
}
func (p *PrivateKey) DpBytes() []byte {
if p.k.Precomputed.Dp == nil {
p.k.Precompute()
}
byteLength := (p.k.Precomputed.Dp.BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.Precomputed.Dp.FillBytes(buf)
return buf
}
func (p *PrivateKey) DqBytes() []byte {
if p.k.Precomputed.Dq == nil {
p.k.Precompute()
}
byteLength := (p.k.Precomputed.Dq.BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.Precomputed.Dq.FillBytes(buf)
return buf
}
func (p *PrivateKey) QiBytes() []byte {
if p.k.Precomputed.Qinv == nil {
p.k.Precompute()
}
byteLength := (p.k.Precomputed.Qinv.BitLen() + 7) / 8 // Round up to the nearest byte
buf := make([]byte, byteLength)
p.k.Precomputed.Qinv.FillBytes(buf)
return buf
}
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
rsaPub := p.k.Public().(*rsa.PublicKey)
return &PublicKey{k: rsaPub}
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
return res
}
func (p *PrivateKey) ToPKCS8PEM() string {
der := p.ToPKCS8DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPrivBlockType,
Bytes: der,
}))
}
// SignToASN1 produce a PKCS#1 v1.5 signature.
// The default signing hash is:
// - SHA-256 for keys of length 2048 bits and under
// - SHA-384 for keys of length 3072 bits and under
// - SHA-512 for higher key length
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hashCode := params.HashOrDefault(defaultSigHash(p.k.N.BitLen()))
hasher := hashCode.New()
hasher.Write(message)
hash := hasher.Sum(nil)
return rsa.SignPKCS1v15(rand.Reader, p.k, stdcrypto.Hash(hashCode), hash)
}

148
crypto/rsa/public.go Normal file
View File

@@ -0,0 +1,148 @@
package rsa
import (
stdcrypto "crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
type PublicKey struct {
k *rsa.PublicKey
}
func PublicKeyFromPKCS1DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKCS1PublicKey(bytes)
if err != nil {
return nil, err
}
return &PublicKey{k: pub}, nil
}
func PublicKeyFromNE(n, e []byte) (*PublicKey, error) {
nBInt := new(big.Int).SetBytes(n)
// some basic checks
if nBInt.Sign() <= 0 {
return nil, fmt.Errorf("invalid modulus")
}
if nBInt.BitLen() < MinRsaKeyBits {
return nil, fmt.Errorf("key length too small")
}
if nBInt.BitLen() > MaxRsaKeyBits {
return nil, fmt.Errorf("key length too large")
}
if nBInt.Bit(0) == 0 {
return nil, fmt.Errorf("modulus must be odd")
}
eBInt := new(big.Int).SetBytes(e)
// some basic checks
if !eBInt.IsInt64() {
return nil, fmt.Errorf("invalid exponent")
}
if eBInt.Sign() <= 0 {
return nil, fmt.Errorf("exponent must be positive")
}
if eBInt.Bit(0) == 0 {
return nil, fmt.Errorf("exponent must be odd")
}
return &PublicKey{k: &rsa.PublicKey{N: nBInt, E: int(eBInt.Int64())}}, nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromX509DER(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(bytes)
if err != nil {
return nil, err
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key")
}
return &PublicKey{k: rsaPub}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) BitLen() int {
return p.k.N.BitLen()
}
func (p *PublicKey) NBytes() []byte {
return p.k.N.Bytes()
}
func (p *PublicKey) EBytes() []byte {
return new(big.Int).SetInt64(int64(p.k.E)).Bytes()
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return p.k.Equal(other.k)
}
return false
}
func (p *PublicKey) ToPublicKeyMultibase() string {
bytes := p.ToX509DER()
return helpers.PublicKeyMultibaseEncode(MultibaseCode, bytes)
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey(p.k)
return res
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
// VerifyASN1 verifies a PKCS#1 v1.5 signature.
// The default signing hash is:
// - SHA-256 for keys of length 2048 bits and under
// - SHA-384 for keys of length 3072 bits and under
// - SHA-512 for higher key length
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
hashCode := params.HashOrDefault(defaultSigHash(p.k.N.BitLen()))
hasher := hashCode.New()
hasher.Write(message)
hash := hasher.Sum(nil)
err := rsa.VerifyPKCS1v15(p.k, stdcrypto.Hash(hashCode), hash, signature)
return err == nil
}

43
crypto/secp256k1/key.go Normal file
View File

@@ -0,0 +1,43 @@
package secp256k1
import (
"encoding/asn1"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
const (
// PublicKeyBytesSize is the size, in bytes, of public keys in raw bytes.
PublicKeyBytesSize = secp256k1.PubKeyBytesLenCompressed
// PrivateKeyBytesSize is the size, in bytes, of private keys in raw bytes.
PrivateKeyBytesSize = secp256k1.PrivKeyBytesLen
// SignatureBytesSize is the size, in bytes, of signatures in raw bytes.
SignatureBytesSize = 64
MultibaseCode = uint64(0xe7)
// coordinateSize is the size, in bytes, of one coordinate in the elliptic curve.
coordinateSize = 32
)
func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
priv, err := secp256k1.GeneratePrivateKey()
if err != nil {
return nil, nil, err
}
pub := priv.PubKey()
return &PublicKey{k: pub}, &PrivateKey{k: priv}, nil
}
const (
pemPubBlockType = "PUBLIC KEY"
pemPrivBlockType = "PRIVATE KEY"
)
var (
// Elliptic curve public key (OID: 1.2.840.10045.2.1)
oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
// Curve is secp256k1 (OID: 1.3.132.0.10)
oidSecp256k1 = asn1.ObjectIdentifier{1, 3, 132, 0, 10}
)

View File

@@ -0,0 +1,105 @@
package secp256k1
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/require"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_testsuite"
)
var harness = testsuite.TestHarness[*PublicKey, *PrivateKey]{
Name: "secp256k1",
GenerateKeyPair: GenerateKeyPair,
PublicKeyFromBytes: PublicKeyFromBytes,
PublicKeyFromPublicKeyMultibase: PublicKeyFromPublicKeyMultibase,
PublicKeyFromX509DER: PublicKeyFromX509DER,
PublicKeyFromX509PEM: PublicKeyFromX509PEM,
PrivateKeyFromBytes: PrivateKeyFromBytes,
PrivateKeyFromPKCS8DER: PrivateKeyFromPKCS8DER,
PrivateKeyFromPKCS8PEM: PrivateKeyFromPKCS8PEM,
MultibaseCode: MultibaseCode,
DefaultHash: crypto.SHA256,
OtherHashes: []crypto.Hash{crypto.KECCAK_256},
PublicKeyBytesSize: PublicKeyBytesSize,
PrivateKeyBytesSize: PrivateKeyBytesSize,
SignatureBytesSize: SignatureBytesSize,
}
func TestSuite(t *testing.T) {
testsuite.TestSuite(t, harness)
}
func BenchmarkSuite(b *testing.B) {
testsuite.BenchSuite(b, harness)
}
func TestPublicKeyX509(t *testing.T) {
// openssl ecparam -genkey -name secp256k1 | openssl pkcs8 -topk8 -nocrypt -out secp256k1-key.pem
// openssl pkey -in secp256k1-key.pem -pubout -out secp256k1-pubkey.pem
pem := `-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEFVP6HKjIReiiUgrC+t+FjG5u0PXIoBmN
V1MMmoOFfKlrD/HuWUjjlw0mDKZcG7AM7JKPTWMOCcvUR2B8BUO3VQ==
-----END PUBLIC KEY-----
`
pub, err := PublicKeyFromX509PEM(pem)
require.NoError(t, err)
rt := pub.ToX509PEM()
require.Equal(t, pem, rt)
}
func TestPrivateKeyPKCS8(t *testing.T) {
// openssl ecparam -genkey -name secp256k1 | openssl pkcs8 -topk8 -nocrypt -out secp256k1-key.pem
pem := `-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgZW9JcJ1kN+DW2IFgqKJu
KS+39/xVa0n2J+lCr7hYGTihRANCAAQVU/ocqMhF6KJSCsL634WMbm7Q9cigGY1X
Uwyag4V8qWsP8e5ZSOOXDSYMplwbsAzsko9NYw4Jy9RHYHwFQ7dV
-----END PRIVATE KEY-----
`
priv, err := PrivateKeyFromPKCS8PEM(pem)
require.NoError(t, err)
rt := priv.ToPKCS8PEM()
require.Equal(t, pem, rt)
}
func FuzzPrivateKeyFromPKCS8PEM(f *testing.F) {
f.Add(`-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgZW9JcJ1kN+DW2IFgqKJu
KS+39/xVa0n2J+lCr7hYGTihRANCAAQVU/ocqMhF6KJSCsL634WMbm7Q9cigGY1X
Uwyag4V8qWsP8e5ZSOOXDSYMplwbsAzsko9NYw4Jy9RHYHwFQ7dV
-----END PRIVATE KEY-----
`)
f.Fuzz(func(t *testing.T, data string) {
// looking for panics
_, _ = PrivateKeyFromPKCS8PEM(data)
})
}
func TestSignatureASN1(t *testing.T) {
// openssl ecparam -genkey -name secp256k1 -noout -out private.pem
// openssl ec -in private.pem -pubout -out public.pem
// echo -n "message" | openssl dgst -sha256 -sign private.pem -out signature.der
// echo -n "message" | openssl dgst -sha256 -verify public.pem -signature signature.der
pubPem := `-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEszL1+ZFqUMAHjLAyzMW7xMBPZek/8cNj
1qI7EgQooB3f8Sh7JwvXu8cosRnjjvYVvS7OliRsbvuceCQ7HBC4fA==
-----END PUBLIC KEY-----
`
pub, err := PublicKeyFromX509PEM(pubPem)
require.NoError(t, err)
b64sig := `MEYCIQDv5SLy768FbOafzDlrxIeeoEn7tKpYBSK6WcKaOZ6AJAIhAKXV6VAwiPq4uk9TpGyFN5JK
8jZPrQ7hdRR5veKKDX2w`
sig, err := base64.StdEncoding.DecodeString(b64sig)
require.NoError(t, err)
require.True(t, pub.VerifyASN1([]byte("message"), sig))
}

219
crypto/secp256k1/private.go Normal file
View File

@@ -0,0 +1,219 @@
package secp256k1
import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/INFURA/go-did/crypto"
)
var _ crypto.PrivateKeySigningBytes = &PrivateKey{}
var _ crypto.PrivateKeySigningASN1 = &PrivateKey{}
var _ crypto.PrivateKeyKeyExchange = &PrivateKey{}
type PrivateKey struct {
k *secp256k1.PrivateKey
}
// PrivateKeyFromBytes converts a serialized public key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if len(b) != PrivateKeyBytesSize {
return nil, fmt.Errorf("invalid secp256k1 private key size")
}
return &PrivateKey{k: secp256k1.PrivKeyFromBytes(b)}, nil
}
// PrivateKeyFromPKCS8DER decodes a PKCS#8 DER (binary) encoded private key.
func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
// Parse the PKCS#8 structure
var pkcs8 struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
}
if _, err := asn1.Unmarshal(bytes, &pkcs8); err != nil {
return nil, fmt.Errorf("failed to parse PKCS#8 structure: %w", err)
}
// Check if this is an Elliptic curve public key (OID: 1.2.840.10045.2.1)
if !pkcs8.Algo.Algorithm.Equal(oidPublicKeyECDSA) {
return nil, fmt.Errorf("not an EC private key, got OID: %v", pkcs8.Algo.Algorithm)
}
// Extract the curve OID from parameters
var namedCurveOID asn1.ObjectIdentifier
if _, err := asn1.Unmarshal(pkcs8.Algo.Parameters.FullBytes, &namedCurveOID); err != nil {
return nil, fmt.Errorf("failed to parse curve parameters: %w", err)
}
// Check if the curve is secp256k1 (OID: 1.3.132.0.10)
if !namedCurveOID.Equal(oidSecp256k1) {
return nil, fmt.Errorf("unsupported curve, expected secp256k1 (1.3.132.0.10), got: %v", namedCurveOID)
}
// Parse the EC private key structure (RFC 5915)
var ecPrivKey struct {
Version int
PrivateKey []byte
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}
if _, err := asn1.Unmarshal(pkcs8.PrivateKey, &ecPrivKey); err != nil {
return nil, fmt.Errorf("failed to parse alliptic curve private key: %w", err)
}
// Validate the EC private key version
if ecPrivKey.Version != 1 {
return nil, fmt.Errorf("unsupported EC private key version: %d", ecPrivKey.Version)
}
// Validate private key length
if len(ecPrivKey.PrivateKey) != PrivateKeyBytesSize {
return nil, fmt.Errorf("invalid secp256k1 private key length: %d, expected %d", len(ecPrivKey.PrivateKey), PrivateKeyBytesSize)
}
// Create the secp256k1 private key
privKeySecp256k1 := secp256k1.PrivKeyFromBytes(ecPrivKey.PrivateKey)
return &PrivateKey{k: privKeySecp256k1}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPrivBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PrivateKeyFromPKCS8DER(block.Bytes)
}
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return p.k.PubKey().IsEqual(other.k.PubKey())
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
return &PublicKey{k: p.k.PubKey()}
}
func (p *PrivateKey) ToBytes() []byte {
return p.k.Serialize()
}
func (p *PrivateKey) ToPKCS8DER() []byte {
pubkeyBytes := p.k.PubKey().SerializeUncompressed()
// Create the EC private key structure
// This follows RFC 5915 format for EC private keys
ecPrivateKey := struct {
Version int
PrivateKey []byte
Parameters asn1.RawValue `asn1:"optional,explicit,tag:0"`
PublicKey asn1.BitString `asn1:"optional,explicit,tag:1"`
}{
Version: 1,
PrivateKey: p.k.Serialize(),
// Parameters are omitted since they're specified in the algorithm identifier
// Pubkey could be omitted, but we include it to match openssl behavior
PublicKey: asn1.BitString{
Bytes: pubkeyBytes,
BitLength: 8 * len(pubkeyBytes),
},
}
ecPrivKeyDER, err := asn1.Marshal(ecPrivateKey)
if err != nil {
panic(err) // This should not happen with valid key data
}
// Create the PKCS#8 structure
pkcs8 := struct {
Version int
Algo pkix.AlgorithmIdentifier
PrivateKey []byte
}{
Version: 0,
Algo: pkix.AlgorithmIdentifier{
// Elliptic curve public key (OID: 1.2.840.10045.2.1)
Algorithm: oidPublicKeyECDSA,
Parameters: asn1.RawValue{
FullBytes: must(asn1.Marshal(oidSecp256k1)),
},
},
PrivateKey: ecPrivKeyDER,
}
der, err := asn1.Marshal(pkcs8)
if err != nil {
panic(err) // This should not happen with valid key data
}
return der
}
func (p *PrivateKey) ToPKCS8PEM() string {
der := p.ToPKCS8DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPrivBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-256.
func (p *PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
sig := ecdsa.Sign(p.k, hash)
r := sig.R()
s := sig.S()
res := make([]byte, SignatureBytesSize)
r.PutBytesUnchecked(res[:SignatureBytesSize/2])
s.PutBytesUnchecked(res[SignatureBytesSize/2:])
return res, nil
}
// The default signing hash is SHA-256.
func (p *PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
sig := ecdsa.Sign(p.k, hash)
return sig.Serialize(), nil
}
func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
if _, ok := remote.(*PublicKey); ok {
return true
}
return false
}
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
if remote, ok := remote.(*PublicKey); ok {
return secp256k1.GenerateSharedSecret(p.k, remote.k), nil
}
return nil, fmt.Errorf("incompatible public key")
}

211
crypto/secp256k1/public.go Normal file
View File

@@ -0,0 +1,211 @@
package secp256k1
import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKeySigningBytes = &PublicKey{}
var _ crypto.PublicKeySigningASN1 = &PublicKey{}
type PublicKey struct {
k *secp256k1.PublicKey
}
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
// It errors if the slice is not the right size.
func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
pub, err := secp256k1.ParsePubKey(b)
if err != nil {
return nil, err
}
return &PublicKey{k: pub}, nil
}
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
func PublicKeyFromXY(x, y []byte) (*PublicKey, error) {
var xf, yf secp256k1.FieldVal
if xf.SetByteSlice(x) {
return nil, fmt.Errorf("invalid secp255k1 public key")
}
if yf.SetByteSlice(y) {
return nil, fmt.Errorf("invalid secp255k1 public key")
}
return &PublicKey{k: secp256k1.NewPublicKey(&xf, &yf)}, nil
}
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
if err != nil {
return nil, err
}
if code != MultibaseCode {
return nil, fmt.Errorf("invalid code")
}
return PublicKeyFromBytes(bytes)
}
// PublicKeyFromX509DER decodes an X.509 DER (binary) encoded public key.
func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
// Parse the X.509 SubjectPublicKeyInfo structure
var spki struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}
if _, err := asn1.Unmarshal(bytes, &spki); err != nil {
return nil, fmt.Errorf("failed to parse X.509 SubjectPublicKeyInfo: %w", err)
}
// Check if this is an Elliptic curve public key (OID: 1.2.840.10045.2.1)
if !spki.Algorithm.Algorithm.Equal(oidPublicKeyECDSA) {
return nil, fmt.Errorf("not an Elliptic curve public key, got OID: %v", spki.Algorithm.Algorithm)
}
// Extract the curve OID from parameters
var namedCurveOID asn1.ObjectIdentifier
if _, err := asn1.Unmarshal(spki.Algorithm.Parameters.FullBytes, &namedCurveOID); err != nil {
return nil, fmt.Errorf("failed to parse curve parameters: %w", err)
}
// Check if this is secp256k1 (OID: 1.3.132.0.10)
if !namedCurveOID.Equal(oidSecp256k1) {
return nil, fmt.Errorf("unsupported curve, expected secp256k1 (1.3.132.0.10), got: %v", namedCurveOID)
}
pubKey, err := secp256k1.ParsePubKey(spki.SubjectPublicKey.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse secp256k1 public key: %w", err)
}
return &PublicKey{k: pubKey}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
block, _ := pem.Decode([]byte(str))
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
if block.Type != pemPubBlockType {
return nil, fmt.Errorf("incorrect PEM block type")
}
return PublicKeyFromX509DER(block.Bytes)
}
func (p *PublicKey) XBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
p.k.X().FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) YBytes() []byte {
// fixed size buffer that can get allocated on the caller's stack after inlining.
var buf [coordinateSize]byte
p.k.Y().FillBytes(buf[:])
return buf[:]
}
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return p.k.IsEqual(other.k)
}
return false
}
func (p *PublicKey) ToBytes() []byte {
// 33-byte compressed format
return p.k.SerializeCompressed()
}
func (p *PublicKey) ToPublicKeyMultibase() string {
return helpers.PublicKeyMultibaseEncode(MultibaseCode, p.k.SerializeCompressed())
}
func (p *PublicKey) ToX509DER() []byte {
pubKeyBytes := p.k.SerializeUncompressed()
// Create the X.509 SubjectPublicKeyInfo structure
spki := struct {
Algorithm pkix.AlgorithmIdentifier
SubjectPublicKey asn1.BitString
}{
Algorithm: pkix.AlgorithmIdentifier{
Algorithm: oidPublicKeyECDSA,
Parameters: asn1.RawValue{
FullBytes: must(asn1.Marshal(oidSecp256k1)),
},
},
SubjectPublicKey: asn1.BitString{
Bytes: pubKeyBytes,
BitLength: len(pubKeyBytes) * 8,
},
}
der, err := asn1.Marshal(spki)
if err != nil {
panic(err) // This should not happen with valid key data
}
return der
}
func (p *PublicKey) ToX509PEM() string {
der := p.ToX509DER()
return string(pem.EncodeToMemory(&pem.Block{
Type: pemPubBlockType,
Bytes: der,
}))
}
// The default signing hash is SHA-256.
func (p *PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
if len(signature) != SignatureBytesSize {
return false
}
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
var r, s secp256k1.ModNScalar
r.SetByteSlice(signature[:32])
s.SetByteSlice(signature[32:])
return ecdsa.NewSignature(&r, &s).Verify(hash, p.k)
}
// The default signing hash is SHA-256.
func (p *PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
params := crypto.CollectSigningOptions(opts)
hasher := params.HashOrDefault(crypto.SHA256).New()
hasher.Write(message)
hash := hasher.Sum(nil)
sig, err := ecdsa.ParseDERSignature(signature)
if err != nil {
return false
}
return sig.Verify(hash, p.k)
}
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}

View File

@@ -20,7 +20,7 @@ func GenerateKeyPair() (*PublicKey, *PrivateKey, error) {
return nil, nil, err
}
pub := priv.Public().(*ecdh.PublicKey)
return (*PublicKey)(pub), (*PrivateKey)(priv), nil
return &PublicKey{k: pub}, &PrivateKey{k: priv}, nil
}
const (

View File

@@ -11,9 +11,11 @@ import (
"github.com/INFURA/go-did/crypto/ed25519"
)
var _ crypto.KeyExchangePrivateKey = (*PrivateKey)(nil)
var _ crypto.PrivateKeyKeyExchange = (*PrivateKey)(nil)
type PrivateKey ecdh.PrivateKey
type PrivateKey struct {
k *ecdh.PrivateKey
}
// PrivateKeyFromBytes converts a serialized private key to a PrivateKey.
// This compact serialization format is the raw key material, without metadata or structure.
@@ -24,7 +26,7 @@ func PrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if err != nil {
return nil, err
}
return (*PrivateKey)(priv), nil
return &PrivateKey{k: priv}, nil
}
// PrivateKeyFromEd25519 converts an ed25519 private key to a x25519 private key.
@@ -51,8 +53,11 @@ func PrivateKeyFromPKCS8DER(bytes []byte) (*PrivateKey, error) {
if err != nil {
return nil, err
}
ecdhPriv := priv.(*ecdh.PrivateKey)
return (*PrivateKey)(ecdhPriv), nil
ecdhPriv, ok := priv.(*ecdh.PrivateKey)
if !ok {
return nil, fmt.Errorf("invalid private key type")
}
return &PrivateKey{k: ecdhPriv}, nil
}
// PrivateKeyFromPKCS8PEM decodes an PKCS#8 PEM (string) encoded private key.
@@ -69,22 +74,21 @@ func PrivateKeyFromPKCS8PEM(str string) (*PrivateKey, error) {
func (p *PrivateKey) Equal(other crypto.PrivateKey) bool {
if other, ok := other.(*PrivateKey); ok {
return (*ecdh.PrivateKey)(p).Equal((*ecdh.PrivateKey)(other))
return p.k.Equal(other.k)
}
return false
}
func (p *PrivateKey) Public() crypto.PublicKey {
ecdhPub := (*ecdh.PrivateKey)(p).Public().(*ecdh.PublicKey)
return (*PublicKey)(ecdhPub)
return &PublicKey{k: p.k.Public().(*ecdh.PublicKey)}
}
func (p *PrivateKey) ToBytes() []byte {
return (*ecdh.PrivateKey)(p).Bytes()
return p.k.Bytes()
}
func (p *PrivateKey) ToPKCS8DER() []byte {
res, _ := x509.MarshalPKCS8PrivateKey((*ecdh.PrivateKey)(p))
res, _ := x509.MarshalPKCS8PrivateKey(p.k)
return res
}
@@ -105,7 +109,7 @@ func (p *PrivateKey) PublicKeyIsCompatible(remote crypto.PublicKey) bool {
func (p *PrivateKey) KeyExchange(remote crypto.PublicKey) ([]byte, error) {
if local, ok := remote.(*PublicKey); ok {
return (*ecdh.PrivateKey)(p).ECDH((*ecdh.PublicKey)(local))
return p.k.ECDH(local.k)
}
return nil, fmt.Errorf("incompatible public key")
}

View File

@@ -8,13 +8,15 @@ import (
"math/big"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/_helpers"
"github.com/INFURA/go-did/crypto/ed25519"
helpers "github.com/INFURA/go-did/crypto/internal"
)
var _ crypto.PublicKey = (*PublicKey)(nil)
type PublicKey ecdh.PublicKey
type PublicKey struct {
k *ecdh.PublicKey
}
// PublicKeyFromBytes converts a serialized public key to a PublicKey.
// This compact serialization format is the raw key material, without metadata or structure.
@@ -24,7 +26,7 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
if err != nil {
return nil, err
}
return (*PublicKey)(pub), nil
return &PublicKey{k: pub}, nil
}
// PublicKeyFromEd25519 converts an ed25519 public key to a x25519 public key.
@@ -101,8 +103,7 @@ func PublicKeyFromX509DER(bytes []byte) (*PublicKey, error) {
if err != nil {
return nil, err
}
ecdhPub := pub.(*ecdh.PublicKey)
return (*PublicKey)(ecdhPub), nil
return &PublicKey{k: pub.(*ecdh.PublicKey)}, nil
}
// PublicKeyFromX509PEM decodes an X.509 PEM (string) encoded public key.
@@ -119,21 +120,21 @@ func PublicKeyFromX509PEM(str string) (*PublicKey, error) {
func (p *PublicKey) Equal(other crypto.PublicKey) bool {
if other, ok := other.(*PublicKey); ok {
return (*ecdh.PublicKey)(p).Equal((*ecdh.PublicKey)(other))
return p.k.Equal(other.k)
}
return false
}
func (p *PublicKey) ToBytes() []byte {
return (*ecdh.PublicKey)(p).Bytes()
return p.k.Bytes()
}
func (p *PublicKey) ToPublicKeyMultibase() string {
return helpers.PublicKeyMultibaseEncode(MultibaseCode, (*ecdh.PublicKey)(p).Bytes())
return helpers.PublicKeyMultibaseEncode(MultibaseCode, p.k.Bytes())
}
func (p *PublicKey) ToX509DER() []byte {
res, _ := x509.MarshalPKIXPublicKey((*ecdh.PublicKey)(p))
res, _ := x509.MarshalPKIXPublicKey(p.k)
return res
}

View File

@@ -12,7 +12,7 @@ import (
_ "github.com/INFURA/go-did/methods/did-key"
)
func ExampleSignature() {
func Example_signature() {
// errors need to be handled
// 1) Parse the DID string into a DID object
@@ -32,7 +32,7 @@ func ExampleSignature() {
// Output: Signature is valid, verified with method: Ed25519VerificationKey2020 did:key:z6MknwcywUtTy2ADJQ8FH1GcSySKPyKDmyzT4rPEE84XREse#z6MknwcywUtTy2ADJQ8FH1GcSySKPyKDmyzT4rPEE84XREse
}
func ExampleKeyAgreement() {
func Example_keyAgreement() {
// errors need to be handled
// 1) We have a private key for Alice

View File

@@ -8,11 +8,57 @@ import (
_ "github.com/INFURA/go-did/methods/did-key"
"github.com/INFURA/go-did/verifications/ed25519"
"github.com/INFURA/go-did/verifications/jsonwebkey"
"github.com/INFURA/go-did/verifications/x25519"
)
func TestRoundTrip(t *testing.T) {
strDoc := `
for _, tc := range []struct {
name string
strDoc string
assertion func(t *testing.T, doc *Document)
}{
{
name: "ed25519",
strDoc: ed25519Doc,
assertion: func(t *testing.T, doc *Document) {
require.Equal(t, "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", doc.ID())
require.Equal(t, ed25519vm.Type2020, doc.Authentication()[0].Type())
require.Equal(t, ed25519vm.Type2020, doc.Assertion()[0].Type())
require.Equal(t, x25519vm.Type2020, doc.KeyAgreement()[0].Type())
require.Equal(t, ed25519vm.Type2020, doc.CapabilityInvocation()[0].Type())
require.Equal(t, ed25519vm.Type2020, doc.CapabilityDelegation()[0].Type())
},
},
{
name: "jsonWebKey",
strDoc: jsonWebKeyDoc,
assertion: func(t *testing.T, doc *Document) {
require.Equal(t, "did:example:123", doc.ID())
require.Len(t, doc.VerificationMethods(), 6)
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A"].Type())
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A"].Type())
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs"].Type())
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw"].Type())
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY"].Type())
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"].Type())
},
},
} {
t.Run(tc.name, func(t *testing.T) {
doc, err := FromJsonBytes([]byte(tc.strDoc))
require.NoError(t, err)
tc.assertion(t, doc)
roundtrip, err := json.Marshal(doc)
require.NoError(t, err)
requireDocEqual(t, tc.strDoc, string(roundtrip))
})
}
}
const ed25519Doc = `
{
"@context": [
"https://www.w3.org/ns/did/v1",
@@ -46,18 +92,136 @@ func TestRoundTrip(t *testing.T) {
}]
}
`
doc, err := FromJsonBytes([]byte(strDoc))
require.NoError(t, err)
// basic testing
require.Equal(t, "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", doc.ID())
require.Equal(t, ed25519vm.Type, doc.Authentication()[0].Type())
require.Equal(t, ed25519vm.Type, doc.Assertion()[0].Type())
require.Equal(t, x25519vm.Type, doc.KeyAgreement()[0].Type())
require.Equal(t, ed25519vm.Type, doc.CapabilityInvocation()[0].Type())
require.Equal(t, ed25519vm.Type, doc.CapabilityDelegation()[0].Type())
roundtrip, err := json.Marshal(doc)
require.NoError(t, err)
require.JSONEq(t, strDoc, string(roundtrip))
const jsonWebKeyDoc = `
{
"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
"id": "did:example:123",
"verificationMethod": [
{
"id": "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ"
}
},
{
"id": "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "Z4Y3NNOxv0J6tCgqOBFnHnaZhJF6LdulT7z8A-2D5_8",
"y": "i5a2NtJoUKXkLm6q8nOEu9WOkso1Ag6FTUT6k_LMnGk"
}
},
{
"id": "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "RSA",
"e": "AQAB",
"n": "omwsC1AqEk6whvxyOltCFWheSQvv1MExu5RLCMT4jVk9khJKv8JeMXWe3bWHatjPskdf2dlaGkW5QjtOnUKL742mvr4tCldKS3ULIaT1hJInMHHxj2gcubO6eEegACQ4QSu9LO0H-LM_L3DsRABB7Qja8HecpyuspW1Tu_DbqxcSnwendamwL52V17eKhlO4uXwv2HFlxufFHM0KmCJujIKyAxjD_m3q__IiHUVHD1tDIEvLPhG9Azsn3j95d-saIgZzPLhQFiKluGvsjrSkYU5pXVWIsV-B2jtLeeLC14XcYxWDUJ0qVopxkBvdlERcNtgF4dvW4X00EHj4vCljFw"
}
},
{
"id": "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
"y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
}
},
{
"id": "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "GnLl6mDti7a2VUIZP5w6pcRX8q5nvEIgB3Q_5RI2p9F_QVsaAlDN7IG68Jn0dS_F",
"y": "jq4QoAHKiIzezDp88s_cxSPXtuXYFliuCGndgU4Qp8l91xzD1spCmFIzQgVjqvcP"
}
},
{
"id": "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AVlZG23LyXYwlbjbGPMxZbHmJpDSu-IvpuKigEN2pzgWtSo--Rwd-n78nrWnZzeDc187Ln3qHlw5LRGrX4qgLQ-y",
"y": "ANIbFeRdPHf1WYMCUjcPz-ZhecZFybOqLIJjVOlLETH7uPlyG0gEoMWnIZXhQVypPy_HtUiUzdnSEPAylYhHBTX2"
}
}
],
"authentication": [
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
],
"assertionMethod": [
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
],
"capabilityDelegation": [
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
],
"capabilityInvocation": [
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
]
}
`
// requireDocEqual compare two DID JSON document but ignore the ordering inside arrays of VerificationMethods
func requireDocEqual(t *testing.T, expected, actual string) {
propsExpected := map[string]json.RawMessage{}
require.NoError(t, json.Unmarshal([]byte(expected), &propsExpected))
propsActual := map[string]json.RawMessage{}
require.NoError(t, json.Unmarshal([]byte(actual), &propsActual))
require.Equal(t, len(propsExpected), len(propsActual))
for k, v := range propsExpected {
switch k {
case "authentication",
"assertionMethod",
"capabilityDelegation",
"capabilityInvocation",
"verificationMethod":
var arrayExpected, arrayActual any
require.NoError(t, json.Unmarshal(propsExpected[k], &arrayExpected))
require.NoError(t, json.Unmarshal(v, &arrayActual))
require.ElementsMatch(t, arrayExpected, arrayActual, "--> on property \"%s\"", k)
delete(propsExpected, k)
delete(propsActual, k)
default:
require.JSONEq(t, string(v), string(propsActual[k]), "--> on property \"%s\"", k)
}
}
}

4
go.mod
View File

@@ -5,6 +5,8 @@ go 1.23.0
toolchain go1.23.1
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/mr-tron/base58 v1.1.0
github.com/multiformats/go-multibase v0.2.0
github.com/multiformats/go-varint v0.0.7
github.com/stretchr/testify v1.10.0
@@ -13,9 +15,9 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mr-tron/base58 v1.1.0 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

6
go.sum
View File

@@ -1,5 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
@@ -16,6 +20,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -14,7 +14,7 @@ type DID interface {
// Document resolves the DID into a DID Document usable for e.g. signature check.
// This can be simply expanding the DID into a Document, or involve external resolution.
Document() (Document, error)
Document(opts ...ResolutionOption) (Document, error)
// String returns the string representation of the DID.
String() string
@@ -111,8 +111,8 @@ type VerificationMethodKeyAgreement interface {
VerificationMethod
// PrivateKeyIsCompatible checks that the given PrivateKey is compatible with this method.
PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool
PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool
// KeyExchange computes the shared key using the given PrivateKey.
KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error)
KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error)
}

View File

@@ -20,6 +20,11 @@ func (d document) MarshalJSON() ([]byte, error) {
// Maybe it doesn't matter, but the spec contradicts itself.
// See https://github.com/w3c-ccg/did-key-spec/issues/71
vms := []did.VerificationMethod{d.signature}
if d.signature != did.VerificationMethod(d.keyAgreement) {
vms = append(vms, d.keyAgreement)
}
return json.Marshal(struct {
Context []string `json:"@context"`
ID string `json:"id"`
@@ -28,17 +33,17 @@ func (d document) MarshalJSON() ([]byte, error) {
VerificationMethod []did.VerificationMethod `json:"verificationMethod,omitempty"`
Authentication []string `json:"authentication,omitempty"`
AssertionMethod []string `json:"assertionMethod,omitempty"`
KeyAgreement []did.VerificationMethod `json:"keyAgreement,omitempty"`
KeyAgreement []string `json:"keyAgreement,omitempty"`
CapabilityInvocation []string `json:"capabilityInvocation,omitempty"`
CapabilityDelegation []string `json:"capabilityDelegation,omitempty"`
}{
Context: d.Context(),
ID: d.id.String(),
AlsoKnownAs: nil,
VerificationMethod: []did.VerificationMethod{d.signature},
VerificationMethod: vms,
Authentication: []string{d.signature.ID()},
AssertionMethod: []string{d.signature.ID()},
KeyAgreement: []did.VerificationMethod{d.keyAgreement},
KeyAgreement: []string{d.keyAgreement.ID()},
CapabilityInvocation: []string{d.signature.ID()},
CapabilityDelegation: []string{d.signature.ID()},
})
@@ -66,6 +71,11 @@ func (d document) AlsoKnownAs() []*url.URL {
}
func (d document) VerificationMethods() map[string]did.VerificationMethod {
if d.signature == did.VerificationMethod(d.keyAgreement) {
return map[string]did.VerificationMethod{
d.signature.ID(): d.signature,
}
}
return map[string]did.VerificationMethod{
d.signature.ID(): d.signature,
d.keyAgreement.ID(): d.keyAgreement,

View File

@@ -7,20 +7,19 @@ import (
"github.com/stretchr/testify/require"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/methods/did-key/testvectors"
)
func TestDocument(t *testing.T) {
d, err := did.Parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
require.NoError(t, err)
doc, err := d.Document()
doc, err := d.Document(did.WithResolutionHintVerificationMethod("Ed25519VerificationKey2020"))
require.NoError(t, err)
bytes, err := json.MarshalIndent(doc, "", " ")
require.NoError(t, err)
// TODO: https://github.com/w3c-ccg/did-key-spec/issues/71
const expected = `{
"@context": [
"https://www.w3.org/ns/did/v1",
@@ -54,8 +53,113 @@ func TestDocument(t *testing.T) {
}]
}`
require.JSONEq(t, expected, string(bytes))
requireDocEqual(t, expected, string(bytes))
}
// TODO: test vectors:
// https://github.com/w3c-ccg/did-key-spec/tree/main/test-vectors
func TestVectors(t *testing.T) {
for _, filename := range testvectors.AllFiles() {
t.Run(filename, func(t *testing.T) {
vectors, err := testvectors.LoadTestVectors(filename)
require.NoError(t, err)
for _, vector := range vectors {
t.Run(vector.DID, func(t *testing.T) {
t.Log("hint is", vector.ResolutionHint)
require.NotZero(t, vector.Document)
require.NotZero(t, vector.Pub)
require.NotZero(t, vector.Priv)
d, err := did.Parse(vector.DID)
require.NoError(t, err)
var opts []did.ResolutionOption
for _, hint := range vector.ResolutionHint {
opts = append(opts, did.WithResolutionHintVerificationMethod(hint))
}
doc, err := d.Document(opts...)
require.NoError(t, err)
bytes, err := json.MarshalIndent(doc, "", " ")
require.NoError(t, err)
requireDocEqual(t, vector.Document, string(bytes))
})
}
})
}
}
// Some variations in the DID document are legal, so we can't just require.JSONEq() to compare two of them.
// This function does its best to compare two documents, regardless of those variations.
func requireDocEqual(t *testing.T, expected, actual string) {
propsExpected := map[string]json.RawMessage{}
err := json.Unmarshal([]byte(expected), &propsExpected)
require.NoError(t, err)
propsActual := map[string]json.RawMessage{}
err = json.Unmarshal([]byte(actual), &propsActual)
require.NoError(t, err)
require.Equal(t, len(propsExpected), len(propsActual))
// if a VerificationMethod is defined inline in the properties below, we move it to vmExpected and replace it with the VM ID
var vmExpected []json.RawMessage
err = json.Unmarshal(propsExpected["verificationMethod"], &vmExpected)
require.NoError(t, err)
for _, s := range []string{"authentication", "assertionMethod", "keyAgreement", "capabilityInvocation", "capabilityDelegation"} {
var vms []json.RawMessage
err = json.Unmarshal(propsExpected[s], &vms)
require.NoError(t, err)
for _, vmBytes := range vms {
vm := map[string]json.RawMessage{}
if err := json.Unmarshal(vmBytes, &vm); err == nil {
vmExpected = append(vmExpected, vmBytes)
propsExpected[s] = append([]byte("[ "), append(vm["id"], []byte(" ]")...)...)
}
}
}
// Same for actual
var vmActual []json.RawMessage
err = json.Unmarshal(propsActual["verificationMethod"], &vmActual)
require.NoError(t, err)
for _, s := range []string{"authentication", "assertionMethod", "keyAgreement", "capabilityInvocation", "capabilityDelegation"} {
var vms []json.RawMessage
err = json.Unmarshal(propsActual[s], &vms)
require.NoError(t, err)
for _, vmBytes := range vms {
vm := map[string]json.RawMessage{}
if err := json.Unmarshal(vmBytes, &vm); err == nil {
vmActual = append(vmActual, vmBytes)
propsActual[s] = append([]byte("[ "), append(vm["id"], []byte(" ]")...)...)
}
}
}
for k, v := range propsExpected {
switch k {
case "verificationMethod":
// Convert to interface{} slices to normalize JSON formatting
expectedVMs := make([]interface{}, len(vmExpected))
for i, vm := range vmExpected {
var normalized interface{}
err := json.Unmarshal(vm, &normalized)
require.NoError(t, err)
expectedVMs[i] = normalized
}
actualVMs := make([]interface{}, len(vmActual))
for i, vm := range vmActual {
var normalized interface{}
err := json.Unmarshal(vm, &normalized)
require.NoError(t, err)
actualVMs[i] = normalized
}
require.ElementsMatch(t, expectedVMs, actualVMs, "--> on property \"%s\"", k)
default:
require.JSONEq(t, string(v), string(propsActual[k]), "--> on property \"%s\"", k)
}
}
}

View File

@@ -6,12 +6,19 @@ import (
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/_helpers"
allkeys "github.com/INFURA/go-did/crypto/_allkeys"
"github.com/INFURA/go-did/crypto/ed25519"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/p384"
"github.com/INFURA/go-did/crypto/p521"
"github.com/INFURA/go-did/crypto/rsa"
"github.com/INFURA/go-did/crypto/secp256k1"
"github.com/INFURA/go-did/crypto/x25519"
"github.com/INFURA/go-did/verifications/ed25519"
"github.com/INFURA/go-did/verifications/jsonwebkey"
"github.com/INFURA/go-did/verifications/multikey"
p256vm "github.com/INFURA/go-did/verifications/p256"
secp256k1vm "github.com/INFURA/go-did/verifications/secp256k1"
"github.com/INFURA/go-did/verifications/x25519"
)
@@ -21,12 +28,11 @@ func init() {
did.RegisterMethod("key", Decode)
}
var _ did.DID = &DidKey{}
var _ did.DID = DidKey{}
type DidKey struct {
msi string // method-specific identifier, i.e. "12345" in "did:key:12345"
signature did.VerificationMethodSignature
keyAgreement did.VerificationMethodKeyAgreement
msi string // method-specific identifier, i.e. "12345" in "did:key:12345"
pubkey crypto.PublicKey
}
func Decode(identifier string) (did.DID, error) {
@@ -38,57 +44,18 @@ func Decode(identifier string) (did.DID, error) {
msi := identifier[len(keyPrefix):]
code, bytes, err := helpers.PublicKeyMultibaseDecode(msi)
pub, err := allkeys.PublicKeyFromPublicKeyMultibase(msi)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
decoder, ok := decoders[code]
if !ok {
return nil, fmt.Errorf("%w: unsupported did:key multicodec: 0x%x", did.ErrInvalidDid, code)
}
pub, err := decoder(bytes)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
d, err := FromPublicKey(pub)
if err != nil {
return nil, fmt.Errorf("%w: %w", did.ErrInvalidDid, err)
}
return d, nil
return DidKey{msi: msi, pubkey: pub}, nil
}
var decoders = map[uint64]func(b []byte) (crypto.PublicKey, error){
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
func FromPublicKey(pub crypto.PublicKey) did.DID {
return DidKey{msi: pub.ToPublicKeyMultibase(), pubkey: pub}
}
func FromPublicKey(pub crypto.PublicKey) (did.DID, error) {
switch pub := pub.(type) {
case ed25519.PublicKey:
d := DidKey{msi: pub.ToPublicKeyMultibase()}
d.signature = ed25519vm.NewVerificationKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
xpub, err := x25519.PublicKeyFromEd25519(pub)
if err != nil {
return nil, err
}
xmsi := xpub.ToPublicKeyMultibase()
d.keyAgreement = x25519vm.NewKeyAgreementKey2020(fmt.Sprintf("did:key:%s#%s", d.msi, xmsi), xpub, d)
return d, nil
case *p256.PublicKey:
d := DidKey{msi: pub.ToPublicKeyMultibase()}
mk := multikey.NewMultiKey(fmt.Sprintf("did:key:%s#%s", d.msi, d.msi), pub, d)
d.signature = mk
d.keyAgreement = mk
return d, nil
default:
return nil, fmt.Errorf("unsupported public key: %T", pub)
}
}
func FromPrivateKey(priv crypto.PrivateKey) (did.DID, error) {
func FromPrivateKey(priv crypto.PrivateKey) did.DID {
return FromPublicKey(priv.Public().(crypto.PublicKey))
}
@@ -96,12 +63,92 @@ func (d DidKey) Method() string {
return "key"
}
func (d DidKey) Document() (did.Document, error) {
return document{
id: d,
signature: d.signature,
keyAgreement: d.keyAgreement,
}, nil
func (d DidKey) Document(opts ...did.ResolutionOption) (did.Document, error) {
params := did.CollectResolutionOpts(opts)
doc := document{id: d}
mainVmId := fmt.Sprintf("did:key:%s#%s", d.msi, d.msi)
switch pub := d.pubkey.(type) {
case ed25519.PublicKey:
xpub, err := x25519.PublicKeyFromEd25519(pub)
if err != nil {
return nil, err
}
xmsi := xpub.ToPublicKeyMultibase()
xVmId := fmt.Sprintf("did:key:%s#%s", d.msi, xmsi)
switch {
case params.HasVerificationMethodHint(jsonwebkey.Type):
doc.signature = jsonwebkey.NewJsonWebKey2020(mainVmId, pub, d)
doc.keyAgreement = jsonwebkey.NewJsonWebKey2020(xVmId, xpub, d)
case params.HasVerificationMethodHint(multikey.Type):
doc.signature = multikey.NewMultiKey(mainVmId, pub, d)
doc.keyAgreement = multikey.NewMultiKey(xVmId, xpub, d)
default:
if params.HasVerificationMethodHint(ed25519vm.Type2018) {
doc.signature = ed25519vm.NewVerificationKey2018(mainVmId, pub, d)
}
if params.HasVerificationMethodHint(x25519vm.Type2019) {
doc.keyAgreement = x25519vm.NewKeyAgreementKey2019(xVmId, xpub, d)
}
if doc.signature == nil {
doc.signature = ed25519vm.NewVerificationKey2020(mainVmId, pub, d)
}
if doc.keyAgreement == nil {
doc.keyAgreement = x25519vm.NewKeyAgreementKey2020(xVmId, xpub, d)
}
}
case *p256.PublicKey:
switch {
case params.HasVerificationMethodHint(jsonwebkey.Type):
jwk := jsonwebkey.NewJsonWebKey2020(mainVmId, pub, d)
doc.signature = jwk
doc.keyAgreement = jwk
case params.HasVerificationMethodHint(p256vm.Type2021):
vm := p256vm.NewKey2021(mainVmId, pub, d)
doc.signature = vm
doc.keyAgreement = vm
default:
mk := multikey.NewMultiKey(mainVmId, pub, d)
doc.signature = mk
doc.keyAgreement = mk
}
case *secp256k1.PublicKey:
switch {
case params.HasVerificationMethodHint(jsonwebkey.Type):
jwk := jsonwebkey.NewJsonWebKey2020(mainVmId, pub, d)
doc.signature = jwk
doc.keyAgreement = jwk
case params.HasVerificationMethodHint(secp256k1vm.Type2019):
vm := secp256k1vm.NewVerificationKey2019(mainVmId, pub, d)
doc.signature = vm
doc.keyAgreement = vm
default:
mk := multikey.NewMultiKey(mainVmId, pub, d)
doc.signature = mk
doc.keyAgreement = mk
}
case *p384.PublicKey, *p521.PublicKey, *rsa.PublicKey:
switch {
case params.HasVerificationMethodHint(jsonwebkey.Type):
jwk := jsonwebkey.NewJsonWebKey2020(mainVmId, pub, d)
doc.signature = jwk
doc.keyAgreement = jwk
default:
mk := multikey.NewMultiKey(mainVmId, pub, d)
doc.signature = mk
doc.keyAgreement = mk
}
default:
return nil, fmt.Errorf("unsupported public key: %T", pub)
}
return doc, nil
}
func (d DidKey) String() string {
@@ -116,5 +163,8 @@ func (d DidKey) Equal(d2 did.DID) bool {
if d2, ok := d2.(DidKey); ok {
return d.msi == d2.msi
}
if d2, ok := d2.(*DidKey); ok {
return d.msi == d2.msi
}
return false
}

View File

@@ -20,8 +20,7 @@ func ExampleGenerateKeyPair() {
fmt.Println("Private key:", base64.StdEncoding.EncodeToString(priv.ToBytes()))
// Make the associated did:key
dk, err := didkey.FromPrivateKey(priv)
handleErr(err)
dk := didkey.FromPrivateKey(priv)
fmt.Println("Did:", dk.String())
// Produce a signature
@@ -56,6 +55,16 @@ func TestMustParseDIDKey(t *testing.T) {
})
}
func TestFromPublicKey(t *testing.T) {
pub, _, err := ed25519.GenerateKeyPair()
require.NoError(t, err)
dk := didkey.FromPublicKey(pub)
require.Equal(t, "did:key:"+pub.ToPublicKeyMultibase(), dk.String())
doc, err := dk.Document()
require.NoError(t, err)
require.NotEmpty(t, doc)
}
func TestEquivalence(t *testing.T) {
did0A, err := did.Parse("did:key:z6Mkod5Jr3yd5SC7UDueqK4dAAw5xYJYjksy722tA9Boxc4z")
require.NoError(t, err)

View File

@@ -0,0 +1,231 @@
{
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY": {
"verificationKeyPair": {
"id": "#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"publicKeyBase58": "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE",
"privateKeyBase58": "8TXrPTbhefHvcz2vkGsDLBZT2UMeemveLKbdh5JZCvvn"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"verificationMethod": [
{
"id": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
"publicKeyBase58": "25EEkQtcLKsEzQ6JTo9cg4W7NHpaurn4Wg6LaNPFq6JQXnrP91SDviUz7KrJVMJd76CtAZFsRLYzvgX2JGxo2ccUHtuHk7ELCWwrkBDfrXCFVfqJKDootee9iVaF6NpdJtBE"
}
],
"assertionMethod": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"authentication": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"capabilityInvocation": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
],
"capabilityDelegation": [
"did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY#zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY"
]
}
},
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY": {
"verificationKeyPair": {
"id": "#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"publicKeyBase58": "t5QqHdxR4C6QJWJAnk3qVd2DMr4MVFEefdP43i7fLbR5A2qJkE5bqgEtyzpNsDViGEsMKHMdpo7fKbPMhGihbfxz3Dv2Hw36XvprLHBA5DDFSphmy91oHQFdahQMei2HjoE",
"privateKeyBase58": "URWBZN9g2ZfKVdAz1L8pvVwEBqCbGBozt4p8Cootb35"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"verificationMethod": [
{
"id": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY",
"publicKeyBase58": "t5QqHdxR4C6QJWJAnk3qVd2DMr4MVFEefdP43i7fLbR5A2qJkE5bqgEtyzpNsDViGEsMKHMdpo7fKbPMhGihbfxz3Dv2Hw36XvprLHBA5DDFSphmy91oHQFdahQMei2HjoE"
}
],
"assertionMethod": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"authentication": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"capabilityInvocation": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
],
"capabilityDelegation": [
"did:key:zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY#zUC77uxiMKceQoxciSy1xgk3nvP8c8NZXDnaY1xsXZaU5UmsZdnwStUke8Ca8zAdPX3MQTHEMhDTCgfdGU7UrY4RRdVhqZp8FaAaoaXFEVp2ZAM7oj3P45BuTCfc3t9FEGBAEQY"
]
}
},
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW": {
"verificationKeyPair": {
"id": "#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"publicKeyBase58": "25VFRgQEfbJ3Pit6Z3mnZbKPK9BdQYGwdmfdcmderjYZ12BFNQYeowjMN1AYKKKcacF3UH35ZNpBqCR8y8QLeeaGLL7UKdKLcFje3VQnosesDNHsU8jBvtvYmLJusxXsSUBC",
"privateKeyBase58": "48FTGTBBhezV7Ldk5g392NSxP2hwgEgWiSZQkMoNri7E"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"verificationMethod": [
{
"id": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW",
"publicKeyBase58": "25VFRgQEfbJ3Pit6Z3mnZbKPK9BdQYGwdmfdcmderjYZ12BFNQYeowjMN1AYKKKcacF3UH35ZNpBqCR8y8QLeeaGLL7UKdKLcFje3VQnosesDNHsU8jBvtvYmLJusxXsSUBC"
}
],
"assertionMethod": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"authentication": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"capabilityInvocation": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
],
"capabilityDelegation": [
"did:key:zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW#zUC7KKoJk5ttwuuc8pmQDiUmtckEPTwcaFVZe4DSFV7fURuoRnD17D3xkBK3A9tZqdADkTTMKSwNkhjo9Hs6HfgNUXo48TNRaxU6XPLSPdRgMc15jCD5DfN34ixjoVemY62JxnW"
]
}
},
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU": {
"verificationKeyPair": {
"id": "#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"publicKeyBase58": "21LWABB5R6mqxvcU6LWMMt9yCAVyt8C1mHREs1EAX23fLcAEPMK4dWx59Jd6RpJ5geGt881vH9yPzZyC8WpHhS2g296mumPxJA3Aghp9jMoACE13rtTie8FYdgzgUw24eboA",
"privateKeyBase58": "86rp8w6Q7zgDdKqYxZsdTyhZogzwbcR7wf3VQrhV3xLG"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"verificationMethod": [
{
"id": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU",
"publicKeyBase58": "21LWABB5R6mqxvcU6LWMMt9yCAVyt8C1mHREs1EAX23fLcAEPMK4dWx59Jd6RpJ5geGt881vH9yPzZyC8WpHhS2g296mumPxJA3Aghp9jMoACE13rtTie8FYdgzgUw24eboA"
}
],
"assertionMethod": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"authentication": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"capabilityInvocation": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
],
"capabilityDelegation": [
"did:key:zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU#zUC7FB43ErjeTPiBLZ8wWT3aBTL7QnJ6AAZh9opgV5dKkw291mC23yTnKQ2pTcSgLbdKnVJ1ARn6XrwxWqvFg5dRFzCjwSg1j35nRgs5c2nbqkJ4auPTyPtkJ3xcABRNWaDX6QU"
]
}
},
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA": {
"verificationKeyPair": {
"id": "#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"publicKeyBase58": "21XhJ3o4ZSgDgRoyP4Pp8agXMwLycuRa1U6fM4ZzJBxH3gJEQbiuwP3Qh2zNoofNrBKPqp3FgXxGvW84cFwMD29oA7Q9w3L8Sjcc3e9mZqFgs8iWxSsDNRcbQdoYtGaxu11r",
"privateKeyBase58": "5LjJ3yibKGP4zKbNgqeiQ284g8LJYnbF7ZBve7Ke9qZ5"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/bls12381-2020/v1"
],
"id": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"verificationMethod": [
{
"id": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"type": "Bls12381G2Key2020",
"controller": "did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA",
"publicKeyBase58": "21XhJ3o4ZSgDgRoyP4Pp8agXMwLycuRa1U6fM4ZzJBxH3gJEQbiuwP3Qh2zNoofNrBKPqp3FgXxGvW84cFwMD29oA7Q9w3L8Sjcc3e9mZqFgs8iWxSsDNRcbQdoYtGaxu11r"
}
],
"assertionMethod": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"authentication": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"capabilityInvocation": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
],
"capabilityDelegation": [
"did:key:zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA#zUC7FNFB7UinoJ5tqkeEELWLsytHBdHpwQ7wLVFAYRT6vqdr5uC3JPK6BVNNByj4KxvVKXoirT7VuqptSznjRCgvr7Ksuk42zyFw1GJSYNQSKCpjVcrZXoPUbR1P6zHmr97mVdA"
]
}
},
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk": {
"verificationKeyPair": {
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty",
"type": "JsonWebKey2020",
"controller": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"publicKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe",
"d": "S7Z1TuL05WHge8od0_mW8b3sRM747caCffsLwS6JZ-c"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"verificationMethod": [
{
"id": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty",
"type": "JsonWebKey2020",
"controller": "did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk",
"publicKeyJwk": {
"kty": "EC",
"crv": "BLS12381_G1",
"x": "im0OQGMTkh4YEhAl16hQwUQTcOaRqIqThqtSwksFK7WaH6Qywypmc3VIDyydmYTe"
}
}
],
"assertionMethod": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"authentication": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"capabilityInvocation": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
],
"capabilityDelegation": [
"did:key:z5TcCmGLu7HrkT5FTnejDTKcH11LPMQLXMPHTRyzY4KdRvqpPLprH7s1ddWFD38cAkZoiDtofUmJVZyEweUTfwjG5H3znk3ir4tzmuDBUSNbNQ7U6jJqj5bkQLKRaQB1bpFJKGLEq3EBwsfPutL5D7p78kFeLNHznqbf5oGpik7ScaDbGLaTLh1Jtadi6VmPNNd44Cojk#z3tEEysHYz5kkgpfDAByfDVgAuvtSFLHSqoMWmmSZBU1LZtN2sDsAS6RVQSevfxv39kyty"
]
}
}
}

View File

@@ -0,0 +1,293 @@
{
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp": {
"seed": "0000000000000000000000000000000000000000000000000000000000000000",
"verificationKeyPair": {
"id": "#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS"
},
"keyAgreementKeyPair": {
"id": "#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "7By6kV2t2d188odEM4ExAve1UithKT6dLva4dwsDT3ak",
"privateKeyBase58": "6QN8DfuN9hjgHgPvLXqgzqYE3jRRGRrmJQZkd5tL8paR"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"verificationMethod": [
{
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS"
},
{
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"publicKeyBase58": "7By6kV2t2d188odEM4ExAve1UithKT6dLva4dwsDT3ak"
}
],
"assertionMethod": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"authentication": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"capabilityInvocation": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"capabilityDelegation": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
],
"keyAgreement": [
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW"
]
}
},
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG": {
"seed": "0000000000000000000000000000000000000000000000000000000000000001",
"verificationKeyPair": {
"id": "#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt"
},
"keyAgreementKeyPair": {
"id": "#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "FcoNC5NqP9CePWbhfz95iHaEsCjGkZUioK9Ck7Qiw286",
"privateKeyBase58": "HBTcN2MrXNRj9xF9oi8QqYyuEPv3JLLjQKuEgW9oxVKP"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"verificationMethod": [
{
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt"
},
{
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "FcoNC5NqP9CePWbhfz95iHaEsCjGkZUioK9Ck7Qiw286"
}
],
"assertionMethod": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"authentication": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"capabilityInvocation": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"capabilityDelegation": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG"
],
"keyAgreement": [
"did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6LSrHyXiPBhUbvPUtyUCdf32sniiMGPTAesgHrtEa4FePtr"
]
}
},
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf": {
"seed": "0000000000000000000000000000000000000000000000000000000000000002",
"verificationKeyPair": {
"id": "#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH"
},
"keyAgreementKeyPair": {
"id": "#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "A5fe37PAxhX5WNKngBpGHhC1KpNE7nwbK9oX2tqwxYxU",
"privateKeyBase58": "ACa4PPJ1LnPNq1iwS33V3Akh7WtnC71WkKFZ9ccM6sX2"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"verificationMethod": [
{
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "8pM1DN3RiT8vbom5u1sNryaNT1nyL8CTTW3b5PwWXRBH"
},
{
"id": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "A5fe37PAxhX5WNKngBpGHhC1KpNE7nwbK9oX2tqwxYxU"
}
],
"assertionMethod": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"authentication": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"capabilityInvocation": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"capabilityDelegation": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf"
],
"keyAgreement": [
"did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE"
]
}
},
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ": {
"seed": "0000000000000000000000000000000000000000000000000000000000000003",
"verificationKeyPair": {
"id": "#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "HPYVwAQmskwT1qEEeRzhoomyfyupJGASQQtCXSNG8XS2"
},
"keyAgreementKeyPair": {
"id": "#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "7ocvdvQinfqtyQ3Dh9aA7ggoVCQkmfaoueZazHETDv3B",
"privateKeyBase58": "FZrzd1osCnbK6y6MJzMBW1RcVfL524sNKhSbqRwMuwHT"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2018/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"verificationMethod": [
{
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "HPYVwAQmskwT1qEEeRzhoomyfyupJGASQQtCXSNG8XS2"
},
{
"id": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ",
"publicKeyBase58": "7ocvdvQinfqtyQ3Dh9aA7ggoVCQkmfaoueZazHETDv3B"
}
],
"assertionMethod": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"authentication": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"capabilityInvocation": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"capabilityDelegation": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ"
],
"keyAgreement": [
"did:key:z6MkvqoYXQfDDJRv8L4wKzxYeuKyVZBfi9Qo6Ro8MiLH3kDQ#z6LSiUo6AEDat8Ze4nQzDo67SGuHLLwsUGkxndHGUjsywHow"
]
}
},
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU": {
"seed": "0000000000000000000000000000000000000000000000000000000000000005",
"verificationKeyPair": {
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8"
},
"privateKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8",
"d": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU"
}
},
"keyAgreementKeyPair": {
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs"
},
"privateKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs",
"d": "aEAAB3VBFPCQtgF3N__wRiXhMOgeiRGstpPC3gnJ1Eo"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"verificationMethod": [
{
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "_eT7oDCtAC98L31MMx9J0T-w7HR-zuvsY08f9MvKne8"
}
},
{
"id": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU",
"type": "JsonWebKey2020",
"controller": "did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "jRIz3oriXDNZmnb35XQb7K1UIlz3ae1ao1YSqLeBXHs"
}
}
],
"assertionMethod": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"authentication": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"capabilityInvocation": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"capabilityDelegation": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU"
],
"keyAgreement": [
"did:key:z6MkwYMhwTvsq376YBAcJHy3vyRWzBgn5vKfVqqDCgm7XVKU#z6LSmArkPSdTKjEESsExHRrSwUzYUHgDuWDewXc4nocasvFU"
]
}
}
}

View File

@@ -0,0 +1,371 @@
{
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv": {
"verificationMethod": {
"id": "#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM",
"d": "gPh-VvVS8MbvKQ9LSVVmfnxnKjHn4Tqj0bmbpehRlpc"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"verificationMethod": [
{
"id": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "igrFmi0whuihKnj9R3Om1SoMph72wUGeFaBbzG2vzns",
"y": "efsX5b10x8yjyrj4ny3pGfLcY7Xby1KzgqOdqnsrJIM"
}
}
],
"assertionMethod": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"authentication": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"capabilityInvocation": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"capabilityDelegation": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
],
"keyAgreement": [
"did:key:zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv#zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv"
]
}
},
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169": {
"verificationMethod": {
"id": "#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU",
"d": "YjRs6vNvw4sYrzVVY8ipkEpDAD9PFqw1sUnvPRMA-WI"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"verificationMethod": [
{
"id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"type": "JsonWebKey2020",
"controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI",
"y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU"
}
}
],
"assertionMethod": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"authentication": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"capabilityInvocation": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"capabilityDelegation": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
],
"keyAgreement": [
"did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169"
]
}
},
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9": {
"verificationMethod": {
"id": "#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"type": "JsonWebKey2020",
"controller": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv",
"d": "hAyGZNj9031guBCdpAOaZkO-E5m-LKLYnMIq0-msrp8JLctseaOeNTHmP3uKVWwX"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"verificationMethod": [
{
"id": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"type": "JsonWebKey2020",
"controller": "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv"
}
}
],
"assertionMethod": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"authentication": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"capabilityInvocation": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"capabilityDelegation": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
],
"keyAgreement": [
"did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9#z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
]
}
},
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54": {
"verificationMethod": {
"id": "#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"type": "JsonWebKey2020",
"controller": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_",
"d": "Xe1HHeh-UsrJPRNLR_Y06VTrWpZYBXi7a7kiRqCgwnAOlJZPwE-xzL3DIIVMavAL"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"verificationMethod": [
{
"id": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"type": "JsonWebKey2020",
"controller": "did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "CA-iNoHDg1lL8pvX3d1uvExzVfCz7Rn6tW781Ub8K5MrDf2IMPyL0RTDiaLHC1JT",
"y": "Kpnrn8DkXUD3ge4mFxi-DKr0DYO2KuJdwNBrhzLRtfMa3WFMZBiPKUPfJj8dYNl_"
}
}
],
"assertionMethod": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"authentication": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"capabilityInvocation": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"capabilityDelegation": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
],
"keyAgreement": [
"did:key:z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54#z82LkvCwHNreneWpsgPEbV3gu1C6NFJEBg4srfJ5gdxEsMGRJUz2sG9FE42shbn2xkZJh54"
]
}
},
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7": {
"verificationMethod": {
"id": "#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC",
"d": "AHwRaNaGs0jkj_pT6PK2aHep7dJK-yxyoL2bIfVRAceq1baxoiFDo3W14c8E2YZn1k5S53r4a11flhQdaB5guJ_X"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"verificationMethod": [
{
"id": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC"
}
}
],
"assertionMethod": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"authentication": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"capabilityInvocation": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"capabilityDelegation": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
],
"keyAgreement": [
"did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7#z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7"
]
}
},
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f": {
"verificationMethod": {
"id": "#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15",
"d": "AbheZ-AA58LP4BpopCGCLH8ZoMdkdJaVOS6KK2NNmDCisr5_Ifxl-qcunrkOJ0CSauA4LJyNbCWcy28Bo6zgHTXQ"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"verificationMethod": [
{
"id": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"type": "JsonWebKey2020",
"controller": "did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AQgyFy6EwH3_u_KXPw8aTXTY7WSVytmbuJeFpq4U6LipxtSmBJe_jjRzms9qubnwm_fGoHMQlvQ1vzS2YLusR2V0",
"y": "Ab06MCcgoG7dM2I-VppdLV1k3lDoeHMvyYqHVfP05Ep2O7Zu0Qwd6IVzfZi9K0KMDud22wdnGUpUtFukZo0EeO15"
}
}
],
"assertionMethod": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"authentication": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"capabilityInvocation": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"capabilityDelegation": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
],
"keyAgreement": [
"did:key:z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f#z2J9gcGdb2nEyMDmzQYv2QZQcM1vXktvy1Pw4MduSWxGabLZ9XESSWLQgbuPhwnXN7zP7HpTzWqrMTzaY5zWe6hpzJ2jnw4f"
]
}
},
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb": {
"verificationMethod": {
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"type": "P256Key2021",
"controller": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"publicKeyBase58": "ekVhkcBFq3w7jULLkBVye6PwaTuMbhJYuzwFnNcgQAPV",
"privateKeyBase58": "9p4VRzdmhsnq869vQjVCTrRry7u4TtfRxhvBFJTGU2Cp"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/multikey-2021/v1"
],
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"verificationMethod": [
{
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"type": "P256Key2021",
"controller": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"publicKeyBase58": "ekVhkcBFq3w7jULLkBVye6PwaTuMbhJYuzwFnNcgQAPV"
}
],
"assertionMethod": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"authentication": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"capabilityInvocation": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"capabilityDelegation": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
],
"keyAgreement": [
"did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb"
]
}
}
}

View File

@@ -0,0 +1,106 @@
{
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i": {
"publicKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB"
},
"privateKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB",
"d": "Eym3sT4KLwBzo5pl5nY83-hAti92iLQRizkrKe22RbNi9Y1kKOBatdtGaJqFVztZZu5ERGKNuTd5VdsjJeekSbXviVGRtdHNCvgmRZlWA5261AgIUPxMmKW062GmGJbKQvscFfziBgHK6tyDBd8cZavqMFHi-7ilMYF7IsFBcJKM85x_30pnfd4YwhGQIc9hzv238aOwYKg8c-MzYhEVUnL273jaiLVlfZWQ5ca-GXJHmdOb_Y4fE5gpXfPFBseqleXsMp0VuXxCEsN30LIJHYscdPtbzLD3LFbuMJglFbQqYqssqymILGqJ7Tc2mB2LmXevfqRWz5D7A_K1WzvuoQ",
"p": "3CWT55Vc9CmUKavtV11fwwCU3lha99eRsl7Yo6HJLudpKV8zJ5bojTPqrowAjiHxyz3ITPCY3WgSX_q1n_4093U51rYermMfHQmrY_l7EgwxvNNMdsH4uMwHhz5vUNks6svtmkJL4JwQe8HPimHMdruCrPZKs0gajO59uNL-0rk",
"q": "zqcpEWpGAeJS0ZzTElrClcl6iQaElAV-KcOVqSOSm25FA2_QE7Px9FTGTrPDBivH5P9iT7SgAWwPypYiCJeDxZ_Rt1FbQvR0gfhzp9_eZJERd4BPaHdcNoXQXVgqezTxbJha064iJhYKHI72zB4rsBS5o4n7qWvqUSXNMYdV_6U",
"dp": "gcUE8rZxHNyFoiretWktUd2943Nh7Eb-c47FVW_BEA0JSIH9vZCPdOztogaVLTOFPLEmqXQKKDl422sGNVG8F0La3V5tp452gL96cGxXx8O4bf6ATGD7JLPgnDCJnbbna2Daptv9rmFQtiMBHCmaRUMzPJHSZuxR-lF7er-lxsE",
"dq": "Id2bCVOVLXHdiKReor9k7A8cmaAL0gYkasu2lwVRXU9w1-NXAiOXHydVaEhlSXmbRJflkJJVNmZzIAwCf830tko-oAAhKJPPFA2XRoeVdn2fkynf2YrV_cloICP2skI23kkJeW8sAXnTJmL3ZvP6zNxYn8hZCaa5u5qqSdeX7FE",
"qi": "WKIToXXnjl7GDbz7jCNbX9nWYOE5BDNzVmwiVOnyGoTZfwJ_qtgizj7pOapxi6dT9S9mMavmeAi6LAsEe1WUWtaKSNhbNh0PUGGXlXHGlhkS8jI1ot0e-scrHAuACE567YQ4VurpNorPKtZ5UENXIn74DEmt4l5m6902VF3X5Wo"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"verificationMethod": [
{
"id": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"type": "JsonWebKey2020",
"controller": "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
"publicKeyJwk": {
"kty": "RSA",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
"e": "AQAB"
}
}
],
"authentication": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"assertionMethod": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"capabilityDelegation": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"capabilityInvocation": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
],
"keyAgreement": [
"did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i#z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i"
]
}
},
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2": {
"publicKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB"
},
"privateKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB",
"d": "TMq1H-clVG7PihkjCqJbRFLMj9wmx6_qfauYwPBKK-HYfWujdW5vxBO6Q-jpqy7RxhiISmxYCBVuw_BuKMqQtR8Q_G9StBzaWYjHfn3Vp6Poz4umLqOjbI2NWNks_ybpGbd30oAK8V5ZkO04ozJpkN4i92hzK3mIc5-z1HiTNUPMn6cStab0VCn6em_ylltV774CEcRJ3OLgid7OUspRt_rID3qyreYbOulTu5WXHIGEnZDzrciIlz1dbcVldpUhD0VAP5ZErD2uUP5oztBNcTTn0YBF8CrOALuQVdaz_t_sNS3P0kWeT1eQ0QwDskO5Hw-Aey2tFeWk1bQyLoQ1A0jsw8mDbkO2zrGfJoxmVBkueTK-q64_n1kV7W1aeJFRj4NwEWmwcrs8GSOGOn38fGB_Y3Kci04qvD6L0QZbFkAVzcJracnxbTdHCEX0jsAAPbYC8M_8PyrPJvPC4IAAWTRrSRbysb7r7viRf4A1vTK9VT7uYyxj7Kzx2cU12d9QBXYfdQ2744bUE7HqN-Vh2rHvv2l5v6vzBRoZ5_OhHHVeUYwC9LouE9lSVAObbFM-Qe1SvzbbwN91LziI7UzUc_xMAEiNwt6PpnIAWAhdvSRawEllTwUcn89udHd5UhiAcm-RQOqXIdA9Aly6d8TT8R1p-ZnQ_gbZyBZeS39AuvU",
"p": "1p4cypsJeTyVXXc5bQpvzVenPy78OHXtGcFQnbTjW8x1GsvJ-rlHAcjUImd44pgNQNe-iYpeUg3KqfONeedNgQCFd8kP7GoVAd45mEvsGBXvjoCXOBMQlsf8UU_hm_LKhVvTvTmMGoudnNv5qYNDMCGJGzwoG-aSvROlIoXzHmDnusZ-hKsDxM9j0PPz21t99Y_Fr30Oq3FIWXPVmLYmfyZYQkxm9a9WNMkqRbwJuMwGI6V9ABsQ1dW_KJzp_aEBbJLcDr9DsWhm9ErLeAlzyaDYEai6wCtKm9em4LDwCbKhJq3hWEp1sIG-hwx1sk7N4i-b8lBijjEQE-dbSQxUlw",
"q": "yUqMejfrttGujadj7Uf7q91KM7nbQGny4TjD-CqibcFE-s2_DExCgP1wfhUPfJr2uPQDIe4g12uaNoa5GbCSDaQwEmQpurC_5mazt-z-_tbI24hoPQm5Hq67fZz-jDE_3OccLPLIWtajJqmxHbbB5VqskMuXo8KDxPRfBQBhykmb9_5M8pY2ggZOV4shCUn5E9nOnvibvw5Wx4CBtWUtca4rhpd3mVen1d8xCe4xTG_ni_w1lwdxzU1GmRFqgTuZWzL0r2FKzJg7hju1SOEe4tKMxQ-xs2HyNaMM__SLsNmS3lsYZ8r2hqcjEMQQZI0T_O-3BjIpyg986P8j055E0w",
"dp": "DujzJRw6P0L3OYQT6EBmXgSt6NTRzvZaX4SvnhU4CmOc6xynTpTamwQhwLYhjtRzb0LNyO5k-RxeLQpvlL1-A-1OWHEOeyUvim6u36a-ozm659KFLu8cIu2H2PpMuTHX4gXsIuRBmIKEk6YwpRcqbsiVpt-6BZ4yKZKY0Vou9rhSwQYTOhJLc7vYumaIVX_4szumxzdP8pcvKI_EkhRtfj3iudBnAsCIo6gqGKgkoMMD1iwkEALRW5m66w5jrywlVi6pvRiKkmOna2da1V8KvUJAYJGxT7JyP3tu64M_Wd0gFvjTg_fAT1_kJau27YlOAl2-Xso43poH_OoAzIVfxw",
"dq": "XI6Z76z9BxB9mgcpTLc3wzw63XQNnB3bn7JRcjBwhdVD2at3uLjsL5HaAy-98kbzQfJ56kUr9sI0o_Po8yYc0ob3z80c3wpdAx2gb-dbDWVH8KJVhBOPestPzR--cEpJGlNuwkBU3mgplyKaHZamq8a46M-lB5jurEbN1mfpj3GvdSYKzdVCdSFfLqP76eCI1pblinW4b-6w-oVdn0JJ1icHPpkxVmJW-2Hok69iHcqrBtRO9AZpTsTEvKekeI4mIyhYGLi9AzzQyhV0c3GImTXFoutng5t7GyzBUoRpI0W4YeQzYa6TEzGRTylIfGPemATF_OReENp0TlLbb3gsHw",
"qi": "m7uZk4AsOfJ1V2RY8lmEF518toCV7juKuS_b_OUx8B0dRG0_kbF1cH-Tmrgsya3bwkYx5HeZG81rX7SRjh-0nVPOMW3tGqU5U9f59DXqvOItJIJ6wvWvWXnuna2-NstYCotFQWadIKjk4wjEKj-a4NJt4D_F4csyeyqWOH2DiUFzBGGxxdEoD5t_HEeNXuWQ6-SiV0x5ZVMln3TSh7IOMl70Smm8HcQF5mOsWg3N0wIg-yffxPrs6r15TRuW1MfT-bZk2GLrtHF1TkIoT1e00jWK4eBl2oRxiJGONUBMTEHV85Fr0yztnA99AgHnrMbE_4ehvev4h5DEWvFyFuJN_g"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"verificationMethod": [
{
"id": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"type": "JsonWebKey2020",
"controller": "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
"publicKeyJwk": {
"kty": "RSA",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
"e": "AQAB"
}
}
],
"authentication": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"assertionMethod": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"capabilityDelegation": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"capabilityInvocation": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
],
"keyAgreement": [
"did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2#zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2"
]
}
}
}

View File

@@ -0,0 +1,257 @@
{
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme": {
"seed": "9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c",
"verificationKeyPair": {
"id": "#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"publicKeyBase58": "23o6Sau8NxxzXcgSc3PLcNxrzrZpbLeBn1izfv3jbKhuv",
"privateKeyBase58": "AjA4cyPUbbfW5wr6iZeRbJLhgH3qDt6q6LMkRw36KpxT"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"verificationMethod": [
{
"id": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme",
"publicKeyBase58": "23o6Sau8NxxzXcgSc3PLcNxrzrZpbLeBn1izfv3jbKhuv"
}
],
"assertionMethod": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"authentication": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"capabilityInvocation": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"capabilityDelegation": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
],
"keyAgreement": [
"did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme#zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"
]
}
},
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2": {
"seed": "f0f4df55a2b3ff13051ea814a8f24ad00f2e469af73c363ac7e9fb999a9072ed",
"verificationKeyPair": {
"id": "#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"publicKeyBase58": "291KzQhqCPC18PqH83XKhxv1HdqrdnxyS7dh15t2uNRzJ",
"privateKeyBase58": "HDbR1D5W3CoNbUKYzUbHH2PRF1atshtVupXgXTQhNB9E"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"verificationMethod": [
{
"id": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2",
"publicKeyBase58": "291KzQhqCPC18PqH83XKhxv1HdqrdnxyS7dh15t2uNRzJ"
}
],
"assertionMethod": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"authentication": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"capabilityInvocation": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"capabilityDelegation": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
],
"keyAgreement": [
"did:key:zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2#zQ3shtxV1FrJfhqE1dvxYRcCknWNjHc3c5X1y3ZSoPDi2aur2"
]
}
},
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N": {
"seed": "6b0b91287ae3348f8c2f2552d766f30e3604867e34adc37ccbb74a8e6b893e02",
"verificationKeyPair": {
"id": "#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"publicKeyBase58": "oesQ92MLiAkt2pjBcJFbW7H4DvzKJv22cotjYbmC2JEe",
"privateKeyBase58": "8CrrWVdzDnvaS7vS5dd2HetFSebwEN46XEFrNDdtWZSZ"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"verificationMethod": [
{
"id": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N",
"publicKeyBase58": "oesQ92MLiAkt2pjBcJFbW7H4DvzKJv22cotjYbmC2JEe"
}
],
"assertionMethod": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"authentication": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"capabilityInvocation": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"capabilityDelegation": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
],
"keyAgreement": [
"did:key:zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N#zQ3shZc2QzApp2oymGvQbzP8eKheVshBHbU4ZYjeXqwSKEn6N"
]
}
},
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy": {
"seed": "c0a6a7c560d37d7ba81ecee9543721ff48fea3e0fb827d42c1868226540fac15",
"verificationKeyPair": {
"id": "#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"publicKeyBase58": "pg3p1vprqePgUoqfAQ1TTgxhL6zLYhHyzooR1pqLxo9F",
"privateKeyBase58": "Dy2fnt8ba4NmbRBXas9bo1BtYgpYFr6ThpFhJbuA3PRn"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"verificationMethod": [
{
"id": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"publicKeyBase58": "pg3p1vprqePgUoqfAQ1TTgxhL6zLYhHyzooR1pqLxo9F"
}
],
"assertionMethod": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"authentication": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"capabilityInvocation": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"capabilityDelegation": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
],
"keyAgreement": [
"did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy"
]
}
},
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj": {
"seed": "175a232d440be1e0788f25488a73d9416c04b6f924bea6354bf05dd2f1a75133",
"verificationKeyPair": {
"id": "#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"publicKeyBase58": "24waDFAUAS16UpZwQQTXVEAmm17rQRjadjuAeBDW8aqL1",
"privateKeyBase58": "2aA6WgZnPiVMBX3LvKSTg3KaFKyzfKpvEacixB3yyTgv"
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/secp256k1-2019/v1"
],
"id": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"verificationMethod": [
{
"id": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj",
"publicKeyBase58": "24waDFAUAS16UpZwQQTXVEAmm17rQRjadjuAeBDW8aqL1"
}
],
"assertionMethod": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"authentication": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"capabilityInvocation": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"capabilityDelegation": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
],
"keyAgreement": [
"did:key:zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj#zQ3shptjE6JwdkeKN4fcpnYQY3m9Cet3NiHdAfpvSUZBFoKBj"
]
}
},
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS": {
"verificationKeyPair": {
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"type": "JsonWebKey2020",
"controller": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc"
},
"privateKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc",
"d": "J5yKm7OXFsXDEutteGYeT0CAfQJwIlHLSYkQxKtgiyo"
}
},
"didDocument": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"verificationMethod": [
{
"id": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"type": "JsonWebKey2020",
"controller": "did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "TEIJN9vnTq1EXMkqzo7yN_867-foKc2pREv45Fw_QA8",
"y": "9yiymlzdxKCiRbYq7p-ArRB-C1ytjHE-eb7RDTi6rVc"
}
}
],
"assertionMethod": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"authentication": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"capabilityInvocation": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"capabilityDelegation": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
],
"keyAgreement": [
"did:key:zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS#zQ3shjmnWpSDEbYKpaFm4kTs9kXyqG6N2QwCYHNPP4yubqgJS"
]
}
}
}

View File

@@ -0,0 +1,240 @@
package testvectors
import (
"embed"
"encoding/hex"
"encoding/json"
"strings"
"github.com/mr-tron/base58"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/ed25519"
"github.com/INFURA/go-did/crypto/jwk"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/secp256k1"
ed25519vm "github.com/INFURA/go-did/verifications/ed25519"
"github.com/INFURA/go-did/verifications/jsonwebkey"
p256vm "github.com/INFURA/go-did/verifications/p256"
secp256k1vm "github.com/INFURA/go-did/verifications/secp256k1"
)
// Origin: https://github.com/w3c-ccg/did-key-spec/tree/main/test-vectors
// See also: https://github.com/w3c-ccg/did-key-spec/pull/73
//go:embed *.json
var testVectorFiles embed.FS
type Vector struct {
DID string
Pub crypto.PublicKey
Priv crypto.PrivateKey
Document string
// Those test vectors are done in a way that, for example, if the input is a JWK the expected verification method
// is JsonWebKey2020. This field collects those hints so that the future resolution matches the expected document.
ResolutionHint []string
}
func AllFiles() []string {
files, err := testVectorFiles.ReadDir(".")
if err != nil {
panic(err)
}
var res []string
for _, f := range files {
// filter some
switch {
case strings.HasPrefix(f.Name(), "bls"): // BLS is not supported
case strings.HasPrefix(f.Name(), "x25519"): // this file has a complete different structure
default:
res = append(res, f.Name())
}
}
return res
}
func LoadTestVectors(filename string) ([]Vector, error) {
data, err := testVectorFiles.ReadFile(filename)
if err != nil {
return nil, err
}
var res []Vector
var vectorsData map[string]map[string]json.RawMessage
if err := json.Unmarshal(data, &vectorsData); err != nil {
return nil, err
}
for k, v := range vectorsData {
vect := Vector{DID: k}
vect.Document = string(v["didDocument"])
// naked JWK
if v["publicKeyJwk"] != nil {
var pub jwk.PublicJwk
if err = json.Unmarshal(v["publicKeyJwk"], &pub); err != nil {
return nil, err
}
vect.Pub = pub.Pubkey
var priv jwk.PrivateJwk
if err = json.Unmarshal(v["privateKeyJwk"], &priv); err != nil {
return nil, err
}
vect.Priv = priv.Privkey
vect.ResolutionHint = append(vect.ResolutionHint, jsonwebkey.Type)
}
if v["verificationMethod"] != nil {
var vm map[string]json.RawMessage
if err = json.Unmarshal(v["verificationMethod"], &vm); err != nil {
return nil, err
}
var vmType string
if err = json.Unmarshal(vm["type"], &vmType); err != nil {
return nil, err
}
vect.ResolutionHint = append(vect.ResolutionHint, vmType)
if vm["publicKeyJwk"] != nil {
var pub jwk.PublicJwk
if err = json.Unmarshal(vm["publicKeyJwk"], &pub); err != nil {
return nil, err
}
vect.Pub = pub.Pubkey
var priv jwk.PrivateJwk
if err = json.Unmarshal(vm["privateKeyJwk"], &priv); err != nil {
return nil, err
}
vect.Priv = priv.Privkey
}
var pubBytes []byte
if vm["publicKeyBase58"] != nil {
var pubB58 string
if err = json.Unmarshal(vm["publicKeyBase58"], &pubB58); err != nil {
return nil, err
}
pubBytes, err = base58.DecodeAlphabet(pubB58, base58.BTCAlphabet)
if err != nil {
return nil, err
}
}
var privBytes []byte
if vm["privateKeyBase58"] != nil {
var privB58 string
if err = json.Unmarshal(vm["privateKeyBase58"], &privB58); err != nil {
return nil, err
}
privBytes, err = base58.DecodeAlphabet(privB58, base58.BTCAlphabet)
if err != nil {
return nil, err
}
}
switch vmType {
case p256vm.Type2021:
vect.Pub, err = p256.PublicKeyFromBytes(pubBytes)
if err != nil {
return nil, err
}
vect.Priv, err = p256.PrivateKeyFromBytes(privBytes)
if err != nil {
return nil, err
}
}
}
if v["verificationKeyPair"] != nil {
var vkp map[string]json.RawMessage
if err = json.Unmarshal(v["verificationKeyPair"], &vkp); err != nil {
return nil, err
}
var vmType string
if err = json.Unmarshal(vkp["type"], &vmType); err != nil {
return nil, err
}
vect.ResolutionHint = append(vect.ResolutionHint, vmType)
var pubBytes []byte
if vkp["publicKeyBase58"] != nil {
var pubB58 string
if err = json.Unmarshal(vkp["publicKeyBase58"], &pubB58); err != nil {
return nil, err
}
pubBytes, err = base58.DecodeAlphabet(pubB58, base58.BTCAlphabet)
if err != nil {
return nil, err
}
}
var privBytes []byte
if vkp["privateKeyBase58"] != nil {
var privB58 string
if err = json.Unmarshal(vkp["privateKeyBase58"], &privB58); err != nil {
return nil, err
}
privBytes, err = base58.DecodeAlphabet(privB58, base58.BTCAlphabet)
if err != nil {
return nil, err
}
}
switch vmType {
case secp256k1vm.Type2019:
vect.Pub, err = secp256k1.PublicKeyFromBytes(pubBytes)
if err != nil {
return nil, err
}
vect.Priv, err = secp256k1.PrivateKeyFromBytes(privBytes)
if err != nil {
return nil, err
}
case ed25519vm.Type2018:
vect.Pub, err = ed25519.PublicKeyFromBytes(pubBytes)
if err != nil {
return nil, err
}
seed, err := hex.DecodeString(strings.Trim(string(v["seed"]), "\""))
if err != nil {
return nil, err
}
vect.Priv, err = ed25519.PrivateKeyFromSeed(seed)
if err != nil {
return nil, err
}
case jsonwebkey.Type:
var pub jwk.PublicJwk
if err = json.Unmarshal(vkp["publicKeyJwk"], &pub); err != nil {
return nil, err
}
vect.Pub = pub.Pubkey
var priv jwk.PrivateJwk
if err = json.Unmarshal(vkp["privateKeyJwk"], &priv); err != nil {
return nil, err
}
vect.Priv = priv.Privkey
vect.ResolutionHint = append(vect.ResolutionHint, jsonwebkey.Type)
}
}
if v["keyAgreementKeyPair"] != nil {
var kakp map[string]json.RawMessage
if err = json.Unmarshal(v["keyAgreementKeyPair"], &kakp); err != nil {
return nil, err
}
var vmType string
if err = json.Unmarshal(kakp["type"], &vmType); err != nil {
return nil, err
}
vect.ResolutionHint = append(vect.ResolutionHint, vmType)
}
res = append(res, vect)
}
return res, nil
}

View File

@@ -0,0 +1,80 @@
{
"didDocument": {
"did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"verificationMethod": [
{
"id": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
"publicKeyBase58": "4Dy8E9UaZscuPUf2GLxV44RCNL7oxmEXXkgWXaug1WKV"
}
],
"keyAgreement": [
"did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F"
]
},
"did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"verificationMethod": [
{
"id": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha#z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha",
"publicKeyBase58": "J3PiFeuSyLugy4DKn87TwK5cnruRgPtxouzXUqg99Avp"
}
],
"keyAgreement": [
"did:key:z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha#z6LStiZsmxiK4odS4Sb6JmdRFuJ6e1SYP157gtiCyJKfrYha"
]
},
"did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/x25519-2019/v1"
],
"id": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"verificationMethod": [
{
"id": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ#z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ",
"publicKeyBase58": "CgTbngDMe7yHHfxPMvhpaFRpFoQWKgXAgwenJj8PsFDe"
}
],
"keyAgreement": [
"did:key:z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ#z6LSoMdmJz2Djah2P4L9taDmtqeJ6wwd2HhKZvNToBmvaczQ"
]
},
"did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz": {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/jws-2020/v1"
],
"id": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"verificationMethod": [
{
"id": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz#z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"type": "JsonWebKey2020",
"controller": "did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz",
"publicKeyJwk": {
"kty": "OKP",
"crv": "X25519",
"x": "467ap28wHJGEXJAb4mLrokqq8A-txA_KmoQTcj31XzU"
}
}
],
"keyAgreement": [
"did:key:z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz#z6LSrzxMVydCourtpA6JLEYupT7ZUQ34hLfQZfRN5H47zLdz"
]
}
}
}

41
options.go Normal file
View File

@@ -0,0 +1,41 @@
package did
type ResolutionOpts struct {
hintVerificationMethod []string
}
func (opts *ResolutionOpts) HasVerificationMethodHint(hint string) bool {
for _, h := range opts.hintVerificationMethod {
if h == hint {
return true
}
}
return false
}
func CollectResolutionOpts(opts []ResolutionOption) ResolutionOpts {
res := ResolutionOpts{}
for _, opt := range opts {
opt(&res)
}
return res
}
type ResolutionOption func(opts *ResolutionOpts)
// WithResolutionHintVerificationMethod adds a hint for the type of verification method to be used
// when resolving and constructing the DID Document, if possible.
// Hints are expected to be VerificationMethod string types, like ed25519vm.Type.
func WithResolutionHintVerificationMethod(hint string) ResolutionOption {
return func(opts *ResolutionOpts) {
if len(hint) == 0 {
return
}
for _, s := range opts.hintVerificationMethod {
if s == hint {
return
}
}
opts.hintVerificationMethod = append(opts.hintVerificationMethod, hint)
}
}

View File

@@ -21,7 +21,7 @@ func TryAllVerify(methods []VerificationMethodSignature, data []byte, sig []byte
// FindMatchingKeyAgreement tries to find a matching key agreement method for the given private key type.
// It returns the shared key as well as the selected method.
// If no matching method is found, it returns an error.
func FindMatchingKeyAgreement(methods []VerificationMethodKeyAgreement, priv crypto.KeyExchangePrivateKey) ([]byte, VerificationMethodKeyAgreement, error) {
func FindMatchingKeyAgreement(methods []VerificationMethodKeyAgreement, priv crypto.PrivateKeyKeyExchange) ([]byte, VerificationMethodKeyAgreement, error) {
for _, method := range methods {
if method.PrivateKeyIsCompatible(priv) {
key, err := method.KeyExchange(priv)

View File

@@ -0,0 +1,102 @@
package ed25519vm
import (
"encoding/json"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto/ed25519"
)
// Specification: https://w3c-ccg.github.io/lds-ed25519-2018/
const (
JsonLdContext2018 = "https://w3id.org/security/suites/ed25519-2018/v1"
Type2018 = "Ed25519VerificationKey2018"
)
var _ did.VerificationMethodSignature = &VerificationKey2018{}
type VerificationKey2018 struct {
id string
pubkey ed25519.PublicKey
controller string
}
func NewVerificationKey2018(id string, pubkey ed25519.PublicKey, controller did.DID) *VerificationKey2018 {
return &VerificationKey2018{
id: id,
pubkey: pubkey,
controller: controller.String(),
}
}
func (v VerificationKey2018) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{
ID: v.ID(),
Type: v.Type(),
Controller: v.Controller(),
PublicKeyBase58: base58.Encode(v.pubkey.ToBytes()),
})
}
func (v *VerificationKey2018) UnmarshalJSON(bytes []byte) error {
aux := struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{}
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
if aux.Type != v.Type() {
return errors.New("invalid type")
}
v.id = aux.ID
if len(v.id) == 0 {
return errors.New("invalid id")
}
pubBytes, err := base58.Decode(aux.PublicKeyBase58)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
v.pubkey, err = ed25519.PublicKeyFromBytes(pubBytes)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
v.controller = aux.Controller
if !did.HasValidDIDSyntax(v.controller) {
return errors.New("invalid controller")
}
return nil
}
func (v VerificationKey2018) ID() string {
return v.id
}
func (v VerificationKey2018) Type() string {
return Type2018
}
func (v VerificationKey2018) Controller() string {
return v.controller
}
func (v VerificationKey2018) JsonLdContext() string {
return JsonLdContext2018
}
func (v VerificationKey2018) Verify(data []byte, sig []byte) (bool, error) {
return v.pubkey.VerifyBytes(data, sig), nil
}

View File

@@ -0,0 +1,27 @@
package ed25519vm_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
ed25519vm "github.com/INFURA/go-did/verifications/ed25519"
)
func TestJsonRoundTrip2018(t *testing.T) {
data := `{
"id": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG#z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"type": "Ed25519VerificationKey2018",
"controller": "did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG",
"publicKeyBase58": "6ASf5EcmmEHTgDJ4X4ZT5vT6iHVJBXPg5AN5YoTCpGWt"
}`
var vk ed25519vm.VerificationKey2018
err := json.Unmarshal([]byte(data), &vk)
require.NoError(t, err)
bytes, err := json.Marshal(vk)
require.NoError(t, err)
require.JSONEq(t, data, string(bytes))
}

View File

@@ -12,8 +12,8 @@ import (
// Specification: https://w3c.github.io/cg-reports/credentials/CG-FINAL-di-eddsa-2020-20220724/
const (
JsonLdContext = "https://w3id.org/security/suites/ed25519-2020/v1"
Type = "Ed25519VerificationKey2020"
JsonLdContext2020 = "https://w3id.org/security/suites/ed25519-2020/v1"
Type2020 = "Ed25519VerificationKey2020"
)
var _ did.VerificationMethodSignature = &VerificationKey2020{}
@@ -80,7 +80,7 @@ func (v VerificationKey2020) ID() string {
}
func (v VerificationKey2020) Type() string {
return Type
return Type2020
}
func (v VerificationKey2020) Controller() string {
@@ -88,7 +88,7 @@ func (v VerificationKey2020) Controller() string {
}
func (v VerificationKey2020) JsonLdContext() string {
return JsonLdContext
return JsonLdContext2020
}
func (v VerificationKey2020) Verify(data []byte, sig []byte) (bool, error) {

View File

@@ -13,7 +13,7 @@ import (
"github.com/INFURA/go-did/verifications/ed25519"
)
func TestJsonRoundTrip(t *testing.T) {
func TestJsonRoundTrip2020(t *testing.T) {
data := `{
"id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"type": "Ed25519VerificationKey2020",
@@ -30,7 +30,7 @@ func TestJsonRoundTrip(t *testing.T) {
require.JSONEq(t, data, string(bytes))
}
func TestSignature(t *testing.T) {
func TestSignature2020(t *testing.T) {
// test vector from https://datatracker.ietf.org/doc/html/rfc8032#section-7.1
pkHex := "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"

View File

@@ -6,7 +6,10 @@ import (
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/verifications/ed25519"
"github.com/INFURA/go-did/verifications/jsonwebkey"
"github.com/INFURA/go-did/verifications/multikey"
p256vm "github.com/INFURA/go-did/verifications/p256"
secp256k1vm "github.com/INFURA/go-did/verifications/secp256k1"
"github.com/INFURA/go-did/verifications/x25519"
)
@@ -20,12 +23,22 @@ func UnmarshalJSON(data []byte) (did.VerificationMethod, error) {
var res did.VerificationMethod
switch aux.Type {
case ed25519vm.Type:
case ed25519vm.Type2018:
res = &ed25519vm.VerificationKey2018{}
case ed25519vm.Type2020:
res = &ed25519vm.VerificationKey2020{}
case multikey.Type:
res = &multikey.MultiKey{}
case x25519vm.Type:
case p256vm.Type2021:
res = &p256vm.Key2021{}
case secp256k1vm.Type2019:
res = &secp256k1vm.VerificationKey2019{}
case x25519vm.Type2019:
res = &x25519vm.KeyAgreementKey2019{}
case x25519vm.Type2020:
res = &x25519vm.KeyAgreementKey2020{}
case jsonwebkey.Type:
res = &jsonwebkey.JsonWebKey2020{}
default:
return nil, fmt.Errorf("unknown verification type: %s", aux.Type)
}

View File

@@ -0,0 +1,109 @@
package jsonwebkey
import (
"encoding/json"
"errors"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/jwk"
)
// Specification:
// - https://www.w3.org/TR/vc-jws-2020/
// - https://w3c-ccg.github.io/lds-jws2020/
const (
JsonLdContext = "https://w3id.org/security/suites/jws-2020/v1"
Type = "JsonWebKey2020"
)
var _ did.VerificationMethodSignature = &JsonWebKey2020{}
var _ did.VerificationMethodKeyAgreement = &JsonWebKey2020{}
type JsonWebKey2020 struct {
id string
pubkey crypto.PublicKey
controller string
}
func NewJsonWebKey2020(id string, pubkey crypto.PublicKey, controller did.DID) *JsonWebKey2020 {
return &JsonWebKey2020{
id: id,
pubkey: pubkey,
controller: controller.String(),
}
}
func (j JsonWebKey2020) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyJWK jwk.PublicJwk `json:"publicKeyJwk"`
}{
ID: j.ID(),
Type: j.Type(),
Controller: j.Controller(),
PublicKeyJWK: jwk.PublicJwk{Pubkey: j.pubkey},
})
}
func (j *JsonWebKey2020) UnmarshalJSON(bytes []byte) error {
aux := struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyJWK jwk.PublicJwk `json:"publicKeyJwk"`
}{}
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
if aux.Type != j.Type() {
return errors.New("invalid type")
}
j.id = aux.ID
if len(j.id) == 0 {
return errors.New("invalid id")
}
j.controller = aux.Controller
if !did.HasValidDIDSyntax(j.controller) {
return errors.New("invalid controller")
}
j.pubkey = aux.PublicKeyJWK.Pubkey
return nil
}
func (j JsonWebKey2020) ID() string {
return j.id
}
func (j JsonWebKey2020) Type() string {
return Type
}
func (j JsonWebKey2020) Controller() string {
return j.controller
}
func (j JsonWebKey2020) JsonLdContext() string {
return JsonLdContext
}
func (j JsonWebKey2020) Verify(data []byte, sig []byte) (bool, error) {
if pub, ok := j.pubkey.(crypto.PublicKeySigningBytes); ok {
return pub.VerifyBytes(data, sig), nil
}
return false, errors.New("not a signing public key")
}
func (j JsonWebKey2020) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(j.pubkey)
}
func (j JsonWebKey2020) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(j.pubkey)
}

View File

@@ -0,0 +1,102 @@
package jsonwebkey
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestJsonRoundTrip(t *testing.T) {
for _, tc := range []struct {
name string
str string
}{
{
name: "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
str: `{
"id": "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ"
}}`,
},
{
name: "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
str: `{
"id": "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "secp256k1",
"x": "Z4Y3NNOxv0J6tCgqOBFnHnaZhJF6LdulT7z8A-2D5_8",
"y": "i5a2NtJoUKXkLm6q8nOEu9WOkso1Ag6FTUT6k_LMnGk"
}}`,
},
{
name: "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
str: `{
"id": "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "RSA",
"e": "AQAB",
"n": "omwsC1AqEk6whvxyOltCFWheSQvv1MExu5RLCMT4jVk9khJKv8JeMXWe3bWHatjPskdf2dlaGkW5QjtOnUKL742mvr4tCldKS3ULIaT1hJInMHHxj2gcubO6eEegACQ4QSu9LO0H-LM_L3DsRABB7Qja8HecpyuspW1Tu_DbqxcSnwendamwL52V17eKhlO4uXwv2HFlxufFHM0KmCJujIKyAxjD_m3q__IiHUVHD1tDIEvLPhG9Azsn3j95d-saIgZzPLhQFiKluGvsjrSkYU5pXVWIsV-B2jtLeeLC14XcYxWDUJ0qVopxkBvdlERcNtgF4dvW4X00EHj4vCljFw"
}}`,
},
{
name: "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
str: `{
"id": "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-256",
"x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
"y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
}}`,
},
{
name: "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
str: `{
"id": "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-384",
"x": "GnLl6mDti7a2VUIZP5w6pcRX8q5nvEIgB3Q_5RI2p9F_QVsaAlDN7IG68Jn0dS_F",
"y": "jq4QoAHKiIzezDp88s_cxSPXtuXYFliuCGndgU4Qp8l91xzD1spCmFIzQgVjqvcP"
}}`,
},
{
name: "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
str: `{
"id": "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
"type": "JsonWebKey2020",
"controller": "did:example:123",
"publicKeyJwk": {
"kty": "EC",
"crv": "P-521",
"x": "AVlZG23LyXYwlbjbGPMxZbHmJpDSu-IvpuKigEN2pzgWtSo--Rwd-n78nrWnZzeDc187Ln3qHlw5LRGrX4qgLQ-y",
"y": "ANIbFeRdPHf1WYMCUjcPz-ZhecZFybOqLIJjVOlLETH7uPlyG0gEoMWnIZXhQVypPy_HtUiUzdnSEPAylYhHBTX2"
}}`,
},
} {
t.Run(tc.name, func(t *testing.T) {
var jwk JsonWebKey2020
err := json.Unmarshal([]byte(tc.str), &jwk)
require.NoError(t, err)
bytes, err := json.Marshal(jwk)
require.NoError(t, err)
require.JSONEq(t, tc.str, string(bytes))
})
}
}

View File

@@ -7,10 +7,7 @@ import (
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
helpers "github.com/INFURA/go-did/crypto/_helpers"
"github.com/INFURA/go-did/crypto/ed25519"
"github.com/INFURA/go-did/crypto/p256"
"github.com/INFURA/go-did/crypto/x25519"
allkeys "github.com/INFURA/go-did/crypto/_allkeys"
)
// Specification: https://www.w3.org/TR/cid-1.0/#Multikey
@@ -69,31 +66,17 @@ func (m *MultiKey) UnmarshalJSON(bytes []byte) error {
if len(m.id) == 0 {
return errors.New("invalid id")
}
code, pubBytes, err := helpers.PublicKeyMultibaseDecode(aux.PublicKeyMultibase)
if err != nil {
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
decoder, ok := decoders[code]
if !ok {
return fmt.Errorf("unsupported publicKeyMultibase code: %d", code)
}
m.pubkey, err = decoder(pubBytes)
if err != nil {
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
m.controller = aux.Controller
if !did.HasValidDIDSyntax(m.controller) {
return errors.New("invalid controller")
}
return nil
}
var decoders = map[uint64]func(b []byte) (crypto.PublicKey, error){
ed25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return ed25519.PublicKeyFromBytes(b) },
p256.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return p256.PublicKeyFromBytes(b) },
x25519.MultibaseCode: func(b []byte) (crypto.PublicKey, error) { return x25519.PublicKeyFromBytes(b) },
m.pubkey, err = allkeys.PublicKeyFromPublicKeyMultibase(aux.PublicKeyMultibase)
if err != nil {
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
}
return nil
}
func (m MultiKey) ID() string {
@@ -113,16 +96,16 @@ func (m MultiKey) JsonLdContext() string {
}
func (m MultiKey) Verify(data []byte, sig []byte) (bool, error) {
if pub, ok := m.pubkey.(crypto.SigningPublicKey); ok {
if pub, ok := m.pubkey.(crypto.PublicKeySigningBytes); ok {
return pub.VerifyBytes(data, sig), nil
}
return false, errors.New("not a signing public key")
}
func (m MultiKey) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool {
func (m MultiKey) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(m.pubkey)
}
func (m MultiKey) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) {
func (m MultiKey) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(m.pubkey)
}

View File

@@ -0,0 +1,114 @@
package p256vm
import (
"encoding/json"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/p256"
)
// Specification: missing
const (
JsonLdContext2021 = "https://w3id.org/security/suites/multikey-2021/v1"
Type2021 = "P256Key2021"
)
var _ did.VerificationMethodSignature = &Key2021{}
var _ did.VerificationMethodKeyAgreement = &Key2021{}
type Key2021 struct {
id string
pubkey *p256.PublicKey
controller string
}
func NewKey2021(id string, pubkey *p256.PublicKey, controller did.DID) *Key2021 {
return &Key2021{
id: id,
pubkey: pubkey,
controller: controller.String(),
}
}
func (m Key2021) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{
ID: m.ID(),
Type: m.Type(),
Controller: m.Controller(),
PublicKeyBase58: base58.Encode(m.pubkey.ToBytes()),
})
}
func (m *Key2021) UnmarshalJSON(bytes []byte) error {
aux := struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{}
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
if aux.Type != m.Type() {
return errors.New("invalid type")
}
m.id = aux.ID
if len(m.id) == 0 {
return errors.New("invalid id")
}
m.controller = aux.Controller
if !did.HasValidDIDSyntax(m.controller) {
return errors.New("invalid controller")
}
pubBytes, err := base58.Decode(aux.PublicKeyBase58)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
m.pubkey, err = p256.PublicKeyFromBytes(pubBytes)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
return nil
}
func (m Key2021) ID() string {
return m.id
}
func (m Key2021) Type() string {
return Type2021
}
func (m Key2021) Controller() string {
return m.controller
}
func (m Key2021) JsonLdContext() string {
return JsonLdContext2021
}
func (m Key2021) Verify(data []byte, sig []byte) (bool, error) {
return m.pubkey.VerifyBytes(data, sig), nil
}
func (m Key2021) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(m.pubkey)
}
func (m Key2021) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(m.pubkey)
}

View File

@@ -0,0 +1,27 @@
package p256vm_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
p256vm "github.com/INFURA/go-did/verifications/p256"
)
func TestJsonRoundTrip(t *testing.T) {
data := `{
"id": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb#zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"type": "P256Key2021",
"controller": "did:key:zDnaeTiq1PdzvZXUaMdezchcMJQpBdH2VN4pgrrEhMCCbmwSb",
"publicKeyBase58": "ekVhkcBFq3w7jULLkBVye6PwaTuMbhJYuzwFnNcgQAPV"
}`
var mk p256vm.Key2021
err := json.Unmarshal([]byte(data), &mk)
require.NoError(t, err)
bytes, err := json.Marshal(mk)
require.NoError(t, err)
require.JSONEq(t, data, string(bytes))
}

View File

@@ -0,0 +1,114 @@
package secp256k1vm
import (
"encoding/json"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/secp256k1"
)
// Specification: https://w3c-ccg.github.io/lds-ecdsa-secp256k1-2019/
const (
JsonLdContext = "https://w3id.org/security/suites/secp256k1-2019/v1"
Type2019 = "EcdsaSecp256k1VerificationKey2019"
)
var _ did.VerificationMethodSignature = &VerificationKey2019{}
var _ did.VerificationMethodKeyAgreement = &VerificationKey2019{}
type VerificationKey2019 struct {
id string
pubkey *secp256k1.PublicKey
controller string
}
func NewVerificationKey2019(id string, pubkey *secp256k1.PublicKey, controller did.DID) *VerificationKey2019 {
return &VerificationKey2019{
id: id,
pubkey: pubkey,
controller: controller.String(),
}
}
func (vm VerificationKey2019) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{
ID: vm.ID(),
Type: vm.Type(),
Controller: vm.Controller(),
PublicKeyBase58: base58.Encode(vm.pubkey.ToBytes()),
})
}
func (vm *VerificationKey2019) UnmarshalJSON(bytes []byte) error {
aux := struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{}
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
if aux.Type != vm.Type() {
return errors.New("invalid type")
}
vm.id = aux.ID
if len(vm.id) == 0 {
return errors.New("invalid id")
}
vm.controller = aux.Controller
if !did.HasValidDIDSyntax(vm.controller) {
return errors.New("invalid controller")
}
pubBytes, err := base58.Decode(aux.PublicKeyBase58)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
vm.pubkey, err = secp256k1.PublicKeyFromBytes(pubBytes)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
return nil
}
func (vm VerificationKey2019) ID() string {
return vm.id
}
func (vm VerificationKey2019) Type() string {
return Type2019
}
func (vm VerificationKey2019) Controller() string {
return vm.controller
}
func (vm VerificationKey2019) JsonLdContext() string {
return JsonLdContext
}
func (vm VerificationKey2019) Verify(data []byte, sig []byte) (bool, error) {
return vm.pubkey.VerifyBytes(data, sig), nil
}
func (vm VerificationKey2019) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(vm.pubkey)
}
func (vm VerificationKey2019) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(vm.pubkey)
}

View File

@@ -0,0 +1,27 @@
package secp256k1vm_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
secp256k1vm "github.com/INFURA/go-did/verifications/secp256k1"
)
func TestJsonRoundTrip(t *testing.T) {
data := `{
"id": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy#zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"type": "EcdsaSecp256k1VerificationKey2019",
"controller": "did:key:zQ3shadCps5JLAHcZiuX5YUtWHHL8ysBJqFLWvjZDKAWUBGzy",
"publicKeyBase58": "pg3p1vprqePgUoqfAQ1TTgxhL6zLYhHyzooR1pqLxo9F"
}`
var mk secp256k1vm.VerificationKey2019
err := json.Unmarshal([]byte(data), &mk)
require.NoError(t, err)
bytes, err := json.Marshal(mk)
require.NoError(t, err)
require.JSONEq(t, data, string(bytes))
}

View File

@@ -0,0 +1,107 @@
package x25519vm
import (
"encoding/json"
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/INFURA/go-did"
"github.com/INFURA/go-did/crypto"
"github.com/INFURA/go-did/crypto/x25519"
)
// Specification: https://github.com/digitalbazaar/x25519-key-agreement-key-2019
const (
JsonLdContext2019 = "https://w3id.org/security/suites/x25519-2019/v1"
Type2019 = "X25519KeyAgreementKey2019"
)
var _ did.VerificationMethodKeyAgreement = &KeyAgreementKey2019{}
type KeyAgreementKey2019 struct {
id string
pubkey *x25519.PublicKey
controller string
}
func NewKeyAgreementKey2019(id string, pubkey *x25519.PublicKey, controller did.DID) *KeyAgreementKey2019 {
return &KeyAgreementKey2019{
id: id,
pubkey: pubkey,
controller: controller.String(),
}
}
func (k KeyAgreementKey2019) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{
ID: k.ID(),
Type: k.Type(),
Controller: k.Controller(),
PublicKeyBase58: base58.Encode(k.pubkey.ToBytes()),
})
}
func (k *KeyAgreementKey2019) UnmarshalJSON(bytes []byte) error {
aux := struct {
ID string `json:"id"`
Type string `json:"type"`
Controller string `json:"controller"`
PublicKeyBase58 string `json:"publicKeyBase58"`
}{}
err := json.Unmarshal(bytes, &aux)
if err != nil {
return err
}
if aux.Type != k.Type() {
return errors.New("invalid type")
}
k.id = aux.ID
if len(k.id) == 0 {
return errors.New("invalid id")
}
pubBytes, err := base58.Decode(aux.PublicKeyBase58)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
k.pubkey, err = x25519.PublicKeyFromBytes(pubBytes)
if err != nil {
return fmt.Errorf("invalid publicKeyBase58: %w", err)
}
k.controller = aux.Controller
if !did.HasValidDIDSyntax(k.controller) {
return errors.New("invalid controller")
}
return nil
}
func (k KeyAgreementKey2019) ID() string {
return k.id
}
func (k KeyAgreementKey2019) Type() string {
return Type2019
}
func (k KeyAgreementKey2019) Controller() string {
return k.controller
}
func (k KeyAgreementKey2019) JsonLdContext() string {
return JsonLdContext2019
}
func (k KeyAgreementKey2019) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(k.pubkey)
}
func (k KeyAgreementKey2019) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(k.pubkey)
}

View File

@@ -0,0 +1,27 @@
package x25519vm_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
x25519vm "github.com/INFURA/go-did/verifications/x25519"
)
func TestJsonRoundTrip2019(t *testing.T) {
data := `{
"id": "#z6LSkkqoZRC34AEpbkhZCqLDcHQVAxuLpQ7kC8XCXMVUfvjE",
"type": "X25519KeyAgreementKey2019",
"controller": "did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf",
"publicKeyBase58": "A5fe37PAxhX5WNKngBpGHhC1KpNE7nwbK9oX2tqwxYxU"
}`
var vm x25519vm.KeyAgreementKey2019
err := json.Unmarshal([]byte(data), &vm)
require.NoError(t, err)
bytes, err := json.Marshal(vm)
require.NoError(t, err)
require.JSONEq(t, data, string(bytes))
}

View File

@@ -13,8 +13,8 @@ import (
// Specification: https://w3c-ccg.github.io/did-method-key/#ed25519-x25519
const (
JsonLdContext = "https://w3id.org/security/suites/x25519-2020/v1"
Type = "X25519KeyAgreementKey2020"
JsonLdContext2020 = "https://w3id.org/security/suites/x25519-2020/v1"
Type2020 = "X25519KeyAgreementKey2020"
)
var _ did.VerificationMethodKeyAgreement = &KeyAgreementKey2020{}
@@ -81,7 +81,7 @@ func (k KeyAgreementKey2020) ID() string {
}
func (k KeyAgreementKey2020) Type() string {
return Type
return Type2020
}
func (k KeyAgreementKey2020) Controller() string {
@@ -89,13 +89,13 @@ func (k KeyAgreementKey2020) Controller() string {
}
func (k KeyAgreementKey2020) JsonLdContext() string {
return JsonLdContext
return JsonLdContext2020
}
func (k KeyAgreementKey2020) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool {
func (k KeyAgreementKey2020) PrivateKeyIsCompatible(local crypto.PrivateKeyKeyExchange) bool {
return local.PublicKeyIsCompatible(k.pubkey)
}
func (k KeyAgreementKey2020) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) {
func (k KeyAgreementKey2020) KeyExchange(local crypto.PrivateKeyKeyExchange) ([]byte, error) {
return local.KeyExchange(k.pubkey)
}

View File

@@ -9,7 +9,7 @@ import (
x25519vm "github.com/INFURA/go-did/verifications/x25519"
)
func TestJsonRoundTrip(t *testing.T) {
func TestJsonRoundTrip2020(t *testing.T) {
data := `{
"id": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW",
"type": "X25519KeyAgreementKey2020",