diff --git a/crypto/_allkeys/allkeys.go b/crypto/_allkeys/allkeys.go new file mode 100644 index 0000000..1f6d31e --- /dev/null +++ b/crypto/_allkeys/allkeys.go @@ -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) +} diff --git a/crypto/_testsuite/testsuite.go b/crypto/_testsuite/testsuite.go index a2ae9ae..0ecdb38 100644 --- a/crypto/_testsuite/testsuite.go +++ b/crypto/_testsuite/testsuite.go @@ -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 +} diff --git a/crypto/ed25519/key_test.go b/crypto/ed25519/key_test.go index fe8e3a9..2b962a8 100644 --- a/crypto/ed25519/key_test.go +++ b/crypto/ed25519/key_test.go @@ -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, diff --git a/crypto/ed25519/private.go b/crypto/ed25519/private.go index 7bd2aed..12b462b 100644 --- a/crypto/ed25519/private.go +++ b/crypto/ed25519/private.go @@ -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) diff --git a/crypto/ed25519/public.go b/crypto/ed25519/public.go index 76e64ba..e9824e6 100644 --- a/crypto/ed25519/public.go +++ b/crypto/ed25519/public.go @@ -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 diff --git a/crypto/hash.go b/crypto/hash.go new file mode 100644 index 0000000..f92c769 --- /dev/null +++ b/crypto/hash.go @@ -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 +} diff --git a/crypto/interface.go b/crypto/interface.go index 58f19c5..43b0809 100644 --- a/crypto/interface.go +++ b/crypto/interface.go @@ -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. diff --git a/crypto/internal/asn1.go b/crypto/internal/asn1.go new file mode 100644 index 0000000..7f52cc8 --- /dev/null +++ b/crypto/internal/asn1.go @@ -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) + }) +} diff --git a/crypto/_helpers/multibase.go b/crypto/internal/multibase.go similarity index 100% rename from crypto/_helpers/multibase.go rename to crypto/internal/multibase.go diff --git a/crypto/jwk/private.go b/crypto/jwk/private.go new file mode 100644 index 0000000..227412e --- /dev/null +++ b/crypto/jwk/private.go @@ -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"]) + } +} diff --git a/crypto/jwk/private_test.go b/crypto/jwk/private_test.go new file mode 100644 index 0000000..a79d1c8 --- /dev/null +++ b/crypto/jwk/private_test.go @@ -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)) + }) + } +} diff --git a/crypto/jwk/public.go b/crypto/jwk/public.go new file mode 100644 index 0000000..da76d7b --- /dev/null +++ b/crypto/jwk/public.go @@ -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"]) + } +} diff --git a/crypto/jwk/public_test.go b/crypto/jwk/public_test.go new file mode 100644 index 0000000..4782799 --- /dev/null +++ b/crypto/jwk/public_test.go @@ -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)) + }) + } +} diff --git a/crypto/options.go b/crypto/options.go new file mode 100644 index 0000000..c24ab67 --- /dev/null +++ b/crypto/options.go @@ -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 + } +} diff --git a/crypto/p256/key.go b/crypto/p256/key.go index 963576f..e14433d 100644 --- a/crypto/p256/key.go +++ b/crypto/p256/key.go @@ -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 ( diff --git a/crypto/p256/key_test.go b/crypto/p256/key_test.go index 59029a0..cd48c4d 100644 --- a/crypto/p256/key_test.go +++ b/crypto/p256/key_test.go @@ -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)) +} diff --git a/crypto/p256/private.go b/crypto/p256/private.go index adf4fa3..b921fec 100644 --- a/crypto/p256/private.go +++ b/crypto/p256/private.go @@ -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 } diff --git a/crypto/p256/public.go b/crypto/p256/public.go index 6dcb8d7..38403fd 100644 --- a/crypto/p256/public.go +++ b/crypto/p256/public.go @@ -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) } diff --git a/crypto/p384/key.go b/crypto/p384/key.go new file mode 100644 index 0000000..88deef1 --- /dev/null +++ b/crypto/p384/key.go @@ -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" +) diff --git a/crypto/p384/key_test.go b/crypto/p384/key_test.go new file mode 100644 index 0000000..50613b5 --- /dev/null +++ b/crypto/p384/key_test.go @@ -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) +} diff --git a/crypto/p384/private.go b/crypto/p384/private.go new file mode 100644 index 0000000..ddbd255 --- /dev/null +++ b/crypto/p384/private.go @@ -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") +} diff --git a/crypto/p384/public.go b/crypto/p384/public.go new file mode 100644 index 0000000..646e120 --- /dev/null +++ b/crypto/p384/public.go @@ -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) +} diff --git a/crypto/p521/key.go b/crypto/p521/key.go new file mode 100644 index 0000000..7569396 --- /dev/null +++ b/crypto/p521/key.go @@ -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" +) diff --git a/crypto/p521/key_test.go b/crypto/p521/key_test.go new file mode 100644 index 0000000..5e65c6e --- /dev/null +++ b/crypto/p521/key_test.go @@ -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) +} diff --git a/crypto/p521/private.go b/crypto/p521/private.go new file mode 100644 index 0000000..b8e8621 --- /dev/null +++ b/crypto/p521/private.go @@ -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") +} diff --git a/crypto/p521/public.go b/crypto/p521/public.go new file mode 100644 index 0000000..36d1235 --- /dev/null +++ b/crypto/p521/public.go @@ -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) +} diff --git a/crypto/rsa/key.go b/crypto/rsa/key.go new file mode 100644 index 0000000..c60a88b --- /dev/null +++ b/crypto/rsa/key.go @@ -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 + } +} diff --git a/crypto/rsa/key_test.go b/crypto/rsa/key_test.go new file mode 100644 index 0000000..afa5a3f --- /dev/null +++ b/crypto/rsa/key_test.go @@ -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)) +} diff --git a/crypto/rsa/private.go b/crypto/rsa/private.go new file mode 100644 index 0000000..7346bb9 --- /dev/null +++ b/crypto/rsa/private.go @@ -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) +} diff --git a/crypto/rsa/public.go b/crypto/rsa/public.go new file mode 100644 index 0000000..6bd685d --- /dev/null +++ b/crypto/rsa/public.go @@ -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 +} diff --git a/crypto/secp256k1/key.go b/crypto/secp256k1/key.go new file mode 100644 index 0000000..e313ae2 --- /dev/null +++ b/crypto/secp256k1/key.go @@ -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} +) diff --git a/crypto/secp256k1/key_test.go b/crypto/secp256k1/key_test.go new file mode 100644 index 0000000..9e204f4 --- /dev/null +++ b/crypto/secp256k1/key_test.go @@ -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)) +} diff --git a/crypto/secp256k1/private.go b/crypto/secp256k1/private.go new file mode 100644 index 0000000..64552df --- /dev/null +++ b/crypto/secp256k1/private.go @@ -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") +} diff --git a/crypto/secp256k1/public.go b/crypto/secp256k1/public.go new file mode 100644 index 0000000..b4b3927 --- /dev/null +++ b/crypto/secp256k1/public.go @@ -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 +} diff --git a/crypto/x25519/key.go b/crypto/x25519/key.go index 95e7964..fa76bde 100644 --- a/crypto/x25519/key.go +++ b/crypto/x25519/key.go @@ -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 ( diff --git a/crypto/x25519/private.go b/crypto/x25519/private.go index e18cc93..56291a7 100644 --- a/crypto/x25519/private.go +++ b/crypto/x25519/private.go @@ -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") } diff --git a/crypto/x25519/public.go b/crypto/x25519/public.go index 18e02c3..6abdcee 100644 --- a/crypto/x25519/public.go +++ b/crypto/x25519/public.go @@ -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 } diff --git a/did_test.go b/did_test.go index 48187c0..73ab8f1 100644 --- a/did_test.go +++ b/did_test.go @@ -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 diff --git a/document/document_test.go b/document/document_test.go index 4f9dd68..ff193f7 100644 --- a/document/document_test.go +++ b/document/document_test.go @@ -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) + } + } } diff --git a/go.mod b/go.mod index 5011e7d..28283bd 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index efbce26..cbbeb8a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/interfaces.go b/interfaces.go index 60eb450..4878f62 100644 --- a/interfaces.go +++ b/interfaces.go @@ -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) } diff --git a/methods/did-key/document.go b/methods/did-key/document.go index 74ad91a..b592326 100644 --- a/methods/did-key/document.go +++ b/methods/did-key/document.go @@ -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, diff --git a/methods/did-key/document_test.go b/methods/did-key/document_test.go index c379018..c11ffc1 100644 --- a/methods/did-key/document_test.go +++ b/methods/did-key/document_test.go @@ -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) + } + } +} diff --git a/methods/did-key/key.go b/methods/did-key/key.go index 1091560..9710b8e 100644 --- a/methods/did-key/key.go +++ b/methods/did-key/key.go @@ -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 } diff --git a/methods/did-key/key_test.go b/methods/did-key/key_test.go index 0cd2f65..b39574f 100644 --- a/methods/did-key/key_test.go +++ b/methods/did-key/key_test.go @@ -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) diff --git a/methods/did-key/testvectors/bls12381.json b/methods/did-key/testvectors/bls12381.json new file mode 100644 index 0000000..1731da6 --- /dev/null +++ b/methods/did-key/testvectors/bls12381.json @@ -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" + ] + } + } +} diff --git a/methods/did-key/testvectors/ed25519-x25519.json b/methods/did-key/testvectors/ed25519-x25519.json new file mode 100644 index 0000000..f403ce1 --- /dev/null +++ b/methods/did-key/testvectors/ed25519-x25519.json @@ -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" + ] + } + } +} diff --git a/methods/did-key/testvectors/nist-curves.json b/methods/did-key/testvectors/nist-curves.json new file mode 100644 index 0000000..af3059c --- /dev/null +++ b/methods/did-key/testvectors/nist-curves.json @@ -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" + ] + } + } +} diff --git a/methods/did-key/testvectors/rsa.json b/methods/did-key/testvectors/rsa.json new file mode 100644 index 0000000..b5205e1 --- /dev/null +++ b/methods/did-key/testvectors/rsa.json @@ -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" + ] + } + } +} diff --git a/methods/did-key/testvectors/secp256k1.json b/methods/did-key/testvectors/secp256k1.json new file mode 100644 index 0000000..d7d9948 --- /dev/null +++ b/methods/did-key/testvectors/secp256k1.json @@ -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" + ] + } + } +} diff --git a/methods/did-key/testvectors/vectors.go b/methods/did-key/testvectors/vectors.go new file mode 100644 index 0000000..a10f0e0 --- /dev/null +++ b/methods/did-key/testvectors/vectors.go @@ -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 +} diff --git a/methods/did-key/testvectors/x25519.json b/methods/did-key/testvectors/x25519.json new file mode 100644 index 0000000..6d02e43 --- /dev/null +++ b/methods/did-key/testvectors/x25519.json @@ -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" + ] + } + } +} diff --git a/options.go b/options.go new file mode 100644 index 0000000..cd7c018 --- /dev/null +++ b/options.go @@ -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) + } +} diff --git a/utilities.go b/utilities.go index 0835e24..020ace1 100644 --- a/utilities.go +++ b/utilities.go @@ -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) diff --git a/verifications/ed25519/VerificationKey2018.go b/verifications/ed25519/VerificationKey2018.go new file mode 100644 index 0000000..e7e9c1c --- /dev/null +++ b/verifications/ed25519/VerificationKey2018.go @@ -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 +} diff --git a/verifications/ed25519/VerificationKey2018_test.go b/verifications/ed25519/VerificationKey2018_test.go new file mode 100644 index 0000000..0ed276d --- /dev/null +++ b/verifications/ed25519/VerificationKey2018_test.go @@ -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)) +} diff --git a/verifications/ed25519/VerificationKey2020.go b/verifications/ed25519/VerificationKey2020.go index 7526102..e524e3b 100644 --- a/verifications/ed25519/VerificationKey2020.go +++ b/verifications/ed25519/VerificationKey2020.go @@ -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) { diff --git a/verifications/ed25519/VerificationKey2020_test.go b/verifications/ed25519/VerificationKey2020_test.go index af0c2dd..47ba3e9 100644 --- a/verifications/ed25519/VerificationKey2020_test.go +++ b/verifications/ed25519/VerificationKey2020_test.go @@ -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" diff --git a/verifications/json.go b/verifications/json.go index 361fbf5..43a0f2c 100644 --- a/verifications/json.go +++ b/verifications/json.go @@ -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) } diff --git a/verifications/jsonwebkey/JsonWebKey2020.go b/verifications/jsonwebkey/JsonWebKey2020.go new file mode 100644 index 0000000..ddc1c7f --- /dev/null +++ b/verifications/jsonwebkey/JsonWebKey2020.go @@ -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) +} diff --git a/verifications/jsonwebkey/JsonWebKey2020_test.go b/verifications/jsonwebkey/JsonWebKey2020_test.go new file mode 100644 index 0000000..87a7278 --- /dev/null +++ b/verifications/jsonwebkey/JsonWebKey2020_test.go @@ -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)) + }) + } +} diff --git a/verifications/multikey/multikey.go b/verifications/multikey/multikey.go index 513b723..1ef1207 100644 --- a/verifications/multikey/multikey.go +++ b/verifications/multikey/multikey.go @@ -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) } diff --git a/verifications/p256/key2021.go b/verifications/p256/key2021.go new file mode 100644 index 0000000..ed13b24 --- /dev/null +++ b/verifications/p256/key2021.go @@ -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) +} diff --git a/verifications/p256/key2021_test.go b/verifications/p256/key2021_test.go new file mode 100644 index 0000000..f82a9a1 --- /dev/null +++ b/verifications/p256/key2021_test.go @@ -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)) +} diff --git a/verifications/secp256k1/VerificationKey2019.go b/verifications/secp256k1/VerificationKey2019.go new file mode 100644 index 0000000..dc3b3fd --- /dev/null +++ b/verifications/secp256k1/VerificationKey2019.go @@ -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) +} diff --git a/verifications/secp256k1/VerificationKey2019_test.go b/verifications/secp256k1/VerificationKey2019_test.go new file mode 100644 index 0000000..19515e0 --- /dev/null +++ b/verifications/secp256k1/VerificationKey2019_test.go @@ -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)) +} diff --git a/verifications/x25519/KeyAgreementKey2019.go b/verifications/x25519/KeyAgreementKey2019.go new file mode 100644 index 0000000..3988c06 --- /dev/null +++ b/verifications/x25519/KeyAgreementKey2019.go @@ -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) +} diff --git a/verifications/x25519/KeyAgreementKey2019_test.go b/verifications/x25519/KeyAgreementKey2019_test.go new file mode 100644 index 0000000..a5a6088 --- /dev/null +++ b/verifications/x25519/KeyAgreementKey2019_test.go @@ -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)) +} diff --git a/verifications/x25519/KeyAgreementKey2020.go b/verifications/x25519/KeyAgreementKey2020.go index fc8307b..eedf1f0 100644 --- a/verifications/x25519/KeyAgreementKey2020.go +++ b/verifications/x25519/KeyAgreementKey2020.go @@ -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) } diff --git a/verifications/x25519/KeyAgreementKey2020_test.go b/verifications/x25519/KeyAgreementKey2020_test.go index a48f41c..e03dac2 100644 --- a/verifications/x25519/KeyAgreementKey2020_test.go +++ b/verifications/x25519/KeyAgreementKey2020_test.go @@ -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",