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

Commit

Permalink
Go Mobile support for DID Key (#457)
Browse files Browse the repository at this point in the history
* mobile

* mobile

* move

* handle compressed keys properly

* pr comments

* add tests

* comments and tests
  • Loading branch information
decentralgabe authored Aug 31, 2023
1 parent 1b69542 commit 7522c30
Show file tree
Hide file tree
Showing 22 changed files with 439 additions and 77 deletions.
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

0 comments on commit 7522c30

Please sign in to comment.