Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Go Mobile support for DID Key #457

Merged
merged 11 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.20.7
go-version: 1.21.0

- name: Install Mage
run: go install github.com/magefile/mage
Expand All @@ -38,7 +38,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.20.7
go-version: 1.21.0

- name: Install Mage
run: go install github.com/magefile/mage
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.20.7
go-version: 1.21.0
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@
.DS_Store

# IDE
.idea/
.idea/

# Mobile
*.jar
*.aar
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ When you're ready you may:

| Requirement | Tested Version | Installation Instructions |
|-------------|----------------|--------------------------------------------------------|
| Go | 1.20.7 | [go.dev](https://go.dev/doc/tutorial/compile-install) |
| Go | 1.21.0 | [go.dev](https://go.dev/doc/tutorial/compile-install) |
| Mage | 1.13.0-6 | [magefile.org](https://magefile.org/) |

### Go
Expand All @@ -23,7 +23,7 @@ You may verify your `go` installation via the terminal:

```
$> go version
go version go1.20.7 darwin/amd64
go version go1.21.0 darwin/amd64
```

If you do not have go, we recommend installing it by:
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![godoc ssi-sdk](https://img.shields.io/badge/godoc-ssi--sdk-blue)](https://pkg.go.dev/github.com/TBD54566975/ssi-sdk)
[![go version 1.20.7](https://img.shields.io/badge/go_version-1.20.7-brightgreen)](https://golang.org/)
[![go version 1.21.0](https://img.shields.io/badge/go_version-1.21.0-brightgreen)](https://golang.org/)
[![Go Report Card A+](https://goreportcard.com/badge/github.com/TBD54566975/ssi-sdk)](https://goreportcard.com/report/github.com/TBD54566975/ssi-sdk)
[![license Apache 2](https://img.shields.io/badge/license-Apache%202-black)](https://github.com/TBD54566975/ssi-sdk/blob/main/LICENSE)
[![issues](https://img.shields.io/github/issues/TBD54566975/ssi-sdk)](https://github.com/TBD54566975/ssi-sdk/issues)
Expand Down Expand Up @@ -115,6 +115,11 @@ For information on versioning refer to our [versioning guide](doc/VERSIONING.md)

The latest version is...nothing! No releases have been made.

# Mobile

Using the [gomobile](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile) tool, we can generate a library that can be
used in mobile applications. For more information view the [mobile README](mobile/README.md).

# Examples

A set of code examples can be found in the [examples directory](example). We welcome
Expand Down
3 changes: 2 additions & 1 deletion crypto/jwx/jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ func (k *PublicKeyJWK) ToPublicKey() (gocrypto.PublicKey, error) {
}
k.ALG = alg
}
if IsSupportedJWXSigningVerificationAlgorithm(k.ALG) {

if IsSupportedJWXSigningVerificationAlgorithm(k.ALG) || IsSupportedKeyAgreementType(k.ALG) {
return k.toSupportedPublicKey()
}
if IsExperimentalJWXSigningVerificationAlgorithm(k.ALG) {
Expand Down
21 changes: 21 additions & 0 deletions crypto/jwx/jwk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,27 @@ func TestJWKToPublicKeyJWK(t *testing.T) {
assert.Equal(tt, publicKey, gotPubKey)
})

t.Run("X25519", func(tt *testing.T) {
// known public key
publicKey, _, err := crypto.GenerateX25519Key()
assert.NoError(tt, err)
assert.NotEmpty(tt, publicKey)

// to our representation of a jwk
pubKeyJWK, err := PublicKeyToPublicKeyJWK(testKID, publicKey)
assert.NoError(tt, err)
assert.NotEmpty(tt, pubKeyJWK)

assert.Equal(tt, "OKP", pubKeyJWK.KTY)
assert.Equal(tt, "X25519", pubKeyJWK.CRV)

// convert back
gotPubKey, err := pubKeyJWK.ToPublicKey()
assert.NoError(tt, err)
assert.NotEmpty(tt, gotPubKey)
assert.Equal(tt, publicKey, gotPubKey)
})

t.Run("Dilithium 2", func(tt *testing.T) {
// known private key
publicKey, _, err := crypto.GenerateDilithiumKeyPair(dilithium.Mode2)
Expand Down
15 changes: 14 additions & 1 deletion crypto/jwx/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func jwxVerifier(id string, jwk PublicKeyJWK, key gocrypto.PublicKey) (*Verifier
}
jwk.ALG = alg
}
if !IsSupportedJWXSigningVerificationAlgorithm(jwk.ALG) && !IsExperimentalJWXSigningVerificationAlgorithm(jwk.ALG) {
if !IsSupportedJWXSigningVerificationAlgorithm(jwk.ALG) && !IsSupportedKeyAgreementType(jwk.KTY) {
return nil, fmt.Errorf("unsupported signing/verification algorithm: %s", jwk.ALG)
}
if convertedPubKey, ok := pubKeyForJWX(key); ok {
Expand Down Expand Up @@ -280,6 +280,19 @@ func GetSupportedJWXSigningVerificationAlgorithms() []string {
}
}

func IsSupportedKeyAgreementType(keyAgreementType string) bool {
for _, supported := range GetSupportedKeyAgreementTypes() {
if keyAgreementType == supported {
return true
}
}
return false
}

func GetSupportedKeyAgreementTypes() []string {
return []string{jwa.X25519.String()}
}

// IsExperimentalJWXSigningVerificationAlgorithm returns true if the algorithm is supported for experimental signing or verifying JWXs
func IsExperimentalJWXSigningVerificationAlgorithm(algorithm string) bool {
for _, supported := range GetExperimentalJWXSigningVerificationAlgorithms() {
Expand Down
106 changes: 48 additions & 58 deletions crypto/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"crypto/rsa"
"crypto/x509"
"fmt"
"math/big"
"reflect"

"github.com/btcsuite/btcd/btcec/v2"
Expand Down Expand Up @@ -61,13 +60,11 @@ func GenerateKeyByKeyType(kt KeyType) (crypto.PublicKey, crypto.PrivateKey, erro
return nil, nil, fmt.Errorf("unsupported key type: %s", kt)
}

type Option struct {
Name string
Value any
}
type Option int

var (
ECDSAMarshalCompressed = Option{Name: "ecdsa-compressed", Value: true}
const (
ECDSAMarshalCompressed Option = iota
ECDSAUnmarshalCompressed
)

// PubKeyToBytes constructs a byte representation of a public key, for a set number of supported key types
Expand All @@ -85,11 +82,25 @@ func PubKeyToBytes(key crypto.PublicKey, opts ...Option) ([]byte, error) {
case secp.PublicKey:
return k.SerializeCompressed(), nil
case ecdsa.PublicKey:
curve := k.Curve
if len(opts) == 1 && opts[0].Name == "ecdsa-compressed" && opts[0].Value.(bool) {
if k.Curve == btcec.S256() {
x := new(btcec.FieldVal)
x.SetByteSlice(k.X.Bytes())
y := new(btcec.FieldVal)
y.SetByteSlice(k.Y.Bytes())
return btcec.NewPublicKey(x, y).SerializeCompressed(), nil
}

// check if we should marshal the key in compressed form
if len(opts) == 1 && opts[0] == ECDSAMarshalCompressed {
return elliptic.MarshalCompressed(k.Curve, k.X, k.Y), nil
}
return elliptic.Marshal(curve, k.X, k.Y), nil

// go from ecdsa public key to bytes
pk, err := x509.MarshalPKIXPublicKey(&k)
if err != nil {
return nil, err
}
return pk, nil
case rsa.PublicKey:
return x509.MarshalPKCS1PublicKey(&k), nil
case dilithium.PublicKey:
Expand All @@ -107,7 +118,7 @@ func PubKeyToBytes(key crypto.PublicKey, opts ...Option) ([]byte, error) {

// BytesToPubKey reconstructs a public key given some bytes and a target key type
// It is assumed the key was turned into byte form using the sibling method `PubKeyToBytes`
func BytesToPubKey(keyBytes []byte, kt KeyType) (crypto.PublicKey, error) {
func BytesToPubKey(keyBytes []byte, kt KeyType, opts ...Option) (crypto.PublicKey, error) {
switch kt {
case Ed25519:
return ed25519.PublicKey(keyBytes), nil
Expand All @@ -120,56 +131,35 @@ func BytesToPubKey(keyBytes []byte, kt KeyType) (crypto.PublicKey, error) {
}
return *pubKey, nil
case SECP256k1ECDSA:
x, y := elliptic.Unmarshal(btcec.S256(), keyBytes)
return ecdsa.PublicKey{
Curve: btcec.S256(),
X: x,
Y: y,
}, nil
case P224:
var x, y *big.Int
x, y = elliptic.Unmarshal(elliptic.P224(), keyBytes)
if x == nil || y == nil {
x, y = elliptic.UnmarshalCompressed(elliptic.P224(), keyBytes)
}
return ecdsa.PublicKey{
Curve: elliptic.P224(),
X: x,
Y: y,
}, nil
case P256:
var x, y *big.Int
x, y = elliptic.Unmarshal(elliptic.P256(), keyBytes)
if x == nil || y == nil {
x, y = elliptic.UnmarshalCompressed(elliptic.P256(), keyBytes)
pk, err := secp.ParsePubKey(keyBytes)
if err != nil {
return nil, err
}
return ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}, nil
case P384:
var x, y *big.Int
x, y = elliptic.Unmarshal(elliptic.P384(), keyBytes)
if x == nil || y == nil {
x, y = elliptic.UnmarshalCompressed(elliptic.P384(), keyBytes)
return *pk.ToECDSA(), nil
case P224, P256, P384, P521:
// check if we should unmarshal the key in compressed form
if len(opts) == 1 && opts[0] == ECDSAUnmarshalCompressed {
switch kt {
case P224:
x, y := elliptic.UnmarshalCompressed(elliptic.P224(), keyBytes)
return ecdsa.PublicKey{Curve: elliptic.P224(), X: x, Y: y}, nil
case P256:
x, y := elliptic.UnmarshalCompressed(elliptic.P256(), keyBytes)
return ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}, nil
case P384:
x, y := elliptic.UnmarshalCompressed(elliptic.P384(), keyBytes)
return ecdsa.PublicKey{Curve: elliptic.P384(), X: x, Y: y}, nil
case P521:
x, y := elliptic.UnmarshalCompressed(elliptic.P521(), keyBytes)
return ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y}, nil
}
}
return ecdsa.PublicKey{
Curve: elliptic.P384(),
X: x,
Y: y,
}, nil
case P521:
var x, y *big.Int
x, y = elliptic.Unmarshal(elliptic.P521(), keyBytes)
if x == nil || y == nil {
x, y = elliptic.UnmarshalCompressed(elliptic.P521(), keyBytes)

key, err := x509.ParsePKIXPublicKey(keyBytes)
if err != nil {
return nil, err
}
return ecdsa.PublicKey{
Curve: elliptic.P521(),
X: x,
Y: y,
}, nil
return *key.(*ecdsa.PublicKey), nil
case RSA:
pubKey, err := x509.ParsePKCS1PublicKey(keyBytes)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion did/key/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func GenerateDIDKey(kt crypto.KeyType) (gocrypto.PrivateKey, *DIDKey, error) {
return nil, nil, errors.Wrap(err, "generating key for did:key")
}

pubKeyBytes, err := crypto.PubKeyToBytes(pubKey)
pubKeyBytes, err := crypto.PubKeyToBytes(pubKey, crypto.ECDSAMarshalCompressed)
if err != nil {
return nil, nil, errors.Wrap(err, "converting public key to byte")
}
Expand Down
3 changes: 2 additions & 1 deletion did/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ func DecodeMultibasePublicKeyWithType(data []byte) ([]byte, cryptosuite.LDKeyTyp

// ConstructJWKVerificationMethod builds a DID verification method with a known LD key type as a JWK
func ConstructJWKVerificationMethod(id, controller string, pubKeyBytes []byte, cryptoKeyType crypto.KeyType) (*VerificationMethod, error) {
pubKey, err := crypto.BytesToPubKey(pubKeyBytes, cryptoKeyType)
// TODO(gabe): consider exposing compression as an option instead of a default
pubKey, err := crypto.BytesToPubKey(pubKeyBytes, cryptoKeyType, crypto.ECDSAUnmarshalCompressed)
if err != nil {
return nil, errors.Wrap(err, "converting bytes to public key")
}
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/TBD54566975/ssi-sdk

go 1.19
go 1.21

require (
github.com/bits-and-blooms/bitset v1.8.0
Expand Down Expand Up @@ -59,8 +59,11 @@ require (
github.com/segmentio/asm v1.2.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.6 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,20 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d h1:Ouem7YgI783/xoG5NZUHbg/ggHFOutUUoq1ZRlCCTbM=
golang.org/x/mobile v0.0.0-20230818142238-7088062f872d/go.mod h1:kQNMt2gXlYXNazoSeytBi7knmDN7YS/JzMKFYxgoNxc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -165,6 +170,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60 h1:o4bs4seAAlSiZQAZbO6/RP5XBCZCooQS3Pgc0AUjWts=
golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
Expand Down
9 changes: 6 additions & 3 deletions go.work
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
go 1.20
go 1.21

toolchain go1.21.0

use (
./sd-jwt
.
)
./sd-jwt
./mobile
)
7 changes: 7 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,16 +238,23 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
Loading