{"action":"created","comment":{"url":"https://api.github.com/repos/keybase/client/pulls/comments/165998467","pull_request_review_id":94019119,"id":165998467,"diff_hunk":"@@ -0,0 +1,226 @@\n+package teams\n+\n+import (\n+\t\"bytes\"\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"strings\"\n+\n+\t\"crypto/hmac\"\n+\t\"crypto/rand\"\n+\t\"crypto/sha512\"\n+\t\"encoding/base64\"\n+\n+\t\"github.com/keybase/client/go/libkb\"\n+\t\"github.com/keybase/go-crypto/ed25519\"\n+\t\"golang.org/x/net/context\"\n+\n+\tkeybase1 \"github.com/keybase/client/go/protocol/keybase1\"\n+)\n+\n+const SeitanVersion2 = 2\n+\n+// Seitan tokens v2 have a '+' as the sixth character. We use this\n+// to distinguish from email invite tokens (and team names).\n+const seitanEncodedIKeyV2PlusOffset = 6\n+\n+// \"Invite Key Version 2\"\n+type SeitanIKeyV2 string\n+\n+// IsSeitany is a very conservative check of whether a given string looks\n+// like a Seitan token. We want to err on the side of considering strings\n+// Seitan tokens, since we don't mistakenly want to send botched Seitan\n+// tokens to the server.\n+func IsSeitany(s string) bool {\n+\treturn len(s) > seitanEncodedIKeyV2PlusOffset && strings.IndexByte(s, '+') > 1\n+}\n+\n+func ParseSeitanVersion(s string) (version SeitanVersion, err error) {\n+\tif !IsSeitany(s) {\n+\t\treturn version, errors.New(\"Invalid token, not seitany\")\n+\t} else if s[seitanEncodedIKeyPlusOffset] == '+' {\n+\t\treturn SeitanVersion1, nil\n+\t} else if s[seitanEncodedIKeyV2PlusOffset] == '+' {\n+\t\treturn SeitanVersion2, nil\n+\t}\n+\treturn version, errors.New(\"Invalid token, invalid '+' position\")\n+}\n+\n+func GenerateIKeyV2() (ikey SeitanIKeyV2, err error) {\n+\tstr, err := generateIKey(seitanEncodedIKeyV2PlusOffset)\n+\tif err != nil {\n+\t\treturn ikey, err\n+\t}\n+\treturn SeitanIKeyV2(str), err\n+}\n+\n+// ParseIKeyV2FromString safely creates SeitanIKey value from\n+// plaintext string. Only format is checked - any 18-character token\n+// with '+' character at position 6 can be \"Invite Key\". Alphabet is\n+// not checked, as it is only a hint for token generation and it can\n+// change over time, but we assume that token length stays the same.\n+func ParseIKeyV2FromString(token string) (ikey SeitanIKeyV2, err error) {\n+\tif len(token) != SeitanEncodedIKeyLength {\n+\t\treturn ikey, fmt.Errorf(\"invalid token length: expected %d characters, got %d\", SeitanEncodedIKeyLength, len(token))\n+\t}\n+\tif token[seitanEncodedIKeyV2PlusOffset] != '+' {\n+\t\treturn ikey, fmt.Errorf(\"invalid token format: expected %dth character to be '+'\", seitanEncodedIKeyV2PlusOffset+1)\n+\t}\n+\n+\treturn SeitanIKeyV2(strings.ToLower(token)), nil\n+}\n+\n+func (ikey SeitanIKeyV2) String() string {\n+\treturn strings.ToLower(string(ikey))\n+}\n+\n+// \"Stretched Invite Key\"\n+type SeitanSIKeyV2 [SeitanScryptKeylen]byte\n+\n+func (ikey SeitanIKeyV2) GenerateSIKey() (sikey SeitanSIKeyV2, err error) {\n+\tbuf, err := generateSIKey(ikey.String())\n+\tif err != nil {\n+\t\treturn sikey, err\n+\t}\n+\tcopy(sikey[:], buf)\n+\treturn sikey, nil\n+}\n+\n+func (sikey SeitanSIKeyV2) GenerateTeamInviteID() (id SCTeamInviteID, err error) {\n+\ttype InviteStagePayload struct {\n+\t\tStage   string        `codec:\"stage\" json:\"stage\"`\n+\t\tVersion SeitanVersion `codec:\"version\" json:\"version\"`\n+\t}\n+\n+\tpayload, err := libkb.MsgpackEncode(InviteStagePayload{\n+\t\tStage:   \"invite_id\",\n+\t\tVersion: SeitanVersion2,\n+\t})\n+\tif err != nil {\n+\t\treturn id, err\n+\t}\n+\treturn generateTeamInviteID(sikey[:], payload)\n+}\n+\n+func (sikey SeitanSIKeyV2) generateKeyPair() (key libkb.NaclSigningKeyPair, err error) {\n+\ttype PrivateKeySeedPayload struct {\n+\t\tStage   string        `codec:\"stage\" json:\"stage\"`\n+\t\tVersion SeitanVersion `codec:\"version\" json:\"version\"`\n+\t}\n+\n+\tpayload, err := libkb.MsgpackEncode(PrivateKeySeedPayload{\n+\t\tStage:   \"eddsa\",\n+\t\tVersion: SeitanVersion2,\n+\t})\n+\tif err != nil {\n+\t\treturn key, err\n+\t}\n+\n+\tmac := hmac.New(sha512.New, sikey[:])\n+\t_, err = mac.Write(payload)\n+\tif err != nil {\n+\t\treturn key, err\n+\t}\n+\n+\tseed := mac.Sum(nil)\n+\tseed = seed[0:32]\n+\tpub, priv, err := ed25519.GenerateKey(bytes.NewBuffer(seed))\n+\tif err != nil {\n+\t\treturn key, err\n+\t}\n+\n+\tcopy(key.Public[:], pub[:])\n+\tkey.Private = &libkb.NaclSigningKeyPrivate{}\n+\tcopy(key.Private[:], priv[:])\n+\treturn key, nil\n+}\n+\n+func (sikey SeitanSIKeyV2) generatePackedEncryptedKeyWithSecretKey(secretKey keybase1.Bytes32, gen keybase1.PerTeamKeyGeneration, nonce keybase1.BoxNonce, label keybase1.SeitanKeyLabel) (pkey SeitanPKey, encoded string, err error) {\n+\n+\tkeyPair, err := sikey.generateKeyPair()\n+\tif err != nil {\n+\t\treturn pkey, encoded, err\n+\t}\n+\n+\tvar keyAndLabel keybase1.SeitanKeyAndLabelVersion2\n+\tkeyAndLabel.K = keybase1.SeitanPubKey(keyPair.GetKID().String())\n+\tkeyAndLabel.L = label\n+\n+\tpackedKeyAndLabel, err := libkb.MsgpackEncode(keybase1.NewSeitanKeyAndLabelWithV2(keyAndLabel))\n+\tif err != nil {\n+\t\treturn pkey, encoded, err\n+\t}\n+\treturn packAndEncryptKeyWithSecretKey(secretKey, gen, nonce, packedKeyAndLabel, SeitanVersion2)\n+}\n+\n+func (sikey SeitanSIKeyV2) GeneratePackedEncryptedKey(ctx context.Context, team *Team, label keybase1.SeitanKeyLabel) (pkey SeitanPKey, encoded string, err error) {\n+\tappKey, err := team.SeitanInviteTokenKeyLatest(ctx)\n+\tif err != nil {\n+\t\treturn pkey, encoded, err\n+\t}\n+\n+\tvar nonce keybase1.BoxNonce\n+\tif _, err = rand.Read(nonce[:]); err != nil {\n+\t\treturn pkey, encoded, err\n+\t}\n+\n+\treturn sikey.generatePackedEncryptedKeyWithSecretKey(appKey.Key, appKey.KeyGeneration, nonce, label)\n+}\n+\n+// \"Signature\"\n+type SeitanSig libkb.NaclSignature\n+type SeitanPubKey libkb.NaclSigningKeyPublic\n+\n+func GenerateSeitanSignatureMessage(uid keybase1.UID, eldestSeqno keybase1.Seqno, inviteID SCTeamInviteID, time keybase1.Time) (payload []byte, err error) {\n+\ttype SigPayload struct {\n+\t\tStage       string         `codec:\"stage\" json:\"stage\"`\n+\t\tUID         keybase1.UID   `codec:\"uid\" json:\"uid\"`\n+\t\tEldestSeqno keybase1.Seqno `codec:\"eldest_seqno\" json:\"eldest_seqno\"`\n+\t\tCTime       keybase1.Time  `codec:\"ctime\" json:\"ctime\"`\n+\t\tInviteID    SCTeamInviteID `codec:\"invite_id\" json:\"invite_id\"`\n+\t\tVersion     SeitanVersion  `codec:\"version\" json:\"version\"`\n+\t}\n+\n+\tpayload, err = libkb.MsgpackEncode(SigPayload{\n+\t\tStage:       \"accept\",\n+\t\tVersion:     SeitanVersion2,\n+\t\tInviteID:    inviteID,\n+\t\tUID:         uid,\n+\t\tEldestSeqno: eldestSeqno,\n+\t\tCTime:       time,\n+\t})\n+\treturn payload, err\n+}\n+\n+func VerifySeitanSignatureMessage(pubKey SeitanPubKey, msg []byte, sig SeitanSig) error {\n+\tnaclsig := libkb.NaclSignature(sig)\n+\tvalid := libkb.NaclSigningKeyPublic(pubKey).Verify(msg, &naclsig)\n+\tif !valid {\n+\t\treturn libkb.KeyCannotVerifyError{}\n+\t}\n+\treturn nil\n+}\n+\n+func ImportSeitanPubKey(keyString keybase1.SeitanPubKey) (pubKey SeitanPubKey, err error) {\n+\tkeypair, err := libkb.ImportNaclSigningKeyPairFromHex(string(keyString))\n+\tif err != nil {\n+\t\treturn pubKey, err\n+\t}\n+\treturn SeitanPubKey(keypair.Public), nil\n+}\n+\n+func (sikey SeitanSIKeyV2) GenerateSignature(uid keybase1.UID, eldestSeqno keybase1.Seqno, inviteID SCTeamInviteID, time keybase1.Time) (sig SeitanSig, encoded string, err error) {\n+\tpayload, err := GenerateSeitanSignatureMessage(uid, eldestSeqno, inviteID, time)\n+\tif err != nil {\n+\t\treturn sig, encoded, err\n+\t}\n+\n+\tkeyPair, err := sikey.generateKeyPair()\n+\tif err != nil {\n+\t\treturn sig, encoded, err\n+\t}\n+\n+\tsig = SeitanSig(*keyPair.Private.Sign(payload))","path":"go/teams/seitan_v2.go","position":223,"original_position":223,"commit_id":"bb5b751c43c07f5375fb8ca245129740a4904f1e","original_commit_id":"bb5b751c43c07f5375fb8ca245129740a4904f1e","user":{"login":"joshblum","id":1144020,"avatar_url":"https://avatars0.githubusercontent.com/u/1144020?v=4","gravatar_id":"","url":"https://api.github.com/users/joshblum","html_url":"https://github.com/joshblum","followers_url":"https://api.github.com/users/joshblum/followers","following_url":"https://api.github.com/users/joshblum/following{/other_user}","gists_url":"https://api.github.com/users/joshblum/gists{/gist_id}","starred_url":"https://api.github.com/users/joshblum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joshblum/subscriptions","organizations_url":"https://api.github.com/users/joshblum/orgs","repos_url":"https://api.github.com/users/joshblum/repos","events_url":"https://api.github.com/users/joshblum/events{/privacy}","received_events_url":"https://api.github.com/users/joshblum/received_events","type":"User","site_admin":false},"body":"It's just a raw sig right now -- I was trying to find examples in the code base for which methods to use and somehow settled on the raw sig when I first implemented. I can refactor or leave as is, which do you prefer?","created_at":"2018-02-05T15:07:25Z","updated_at":"2018-02-05T15:23:27Z","html_url":"https://github.com/keybase/client/pull/10333#discussion_r165998467","pull_request_url":"https://api.github.com/repos/keybase/client/pulls/10333","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/keybase/client/pulls/comments/165998467"},"html":{"href":"https://github.com/keybase/client/pull/10333#discussion_r165998467"},"pull_request":{"href":"https://api.github.com/repos/keybase/client/pulls/10333"}},"in_reply_to_id":165794468},"pull_request":{"url":"https://api.github.com/repos/keybase/client/pulls/10333","id":165247141,"html_url":"https://github.com/keybase/client/pull/10333","diff_url":"https://github.com/keybase/client/pull/10333.diff","patch_url":"https://github.com/keybase/client/pull/10333.patch","issue_url":"https://api.github.com/repos/keybase/client/issues/10333","number":10333,"state":"open","locked":false,"title":"Seitan V2 implementation","user":{"login":"joshblum","id":1144020,"avatar_url":"https://avatars0.githubusercontent.com/u/1144020?v=4","gravatar_id":"","url":"https://api.github.com/users/joshblum","html_url":"https://github.com/joshblum","followers_url":"https://api.github.com/users/joshblum/followers","following_url":"https://api.github.com/users/joshblum/following{/other_user}","gists_url":"https://api.github.com/users/joshblum/gists{/gist_id}","starred_url":"https://api.github.com/users/joshblum/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joshblum/subscriptions","organizations_url":"https://api.github.com/users/joshblum/orgs","repos_url":"https://api.github.com/users/joshblum/repos","events_url":"https://api.github.com/users/joshblum/events{/privacy}","received_events_url":"https://api.github.com/users/joshblum/received_events","type":"User","site_admin":false},"body":"In seitan v2 we address a security issue where a nefarious reader, when colluding with an evil server, could steal an invite generated by a different admin and invite someone else to the team. The issue was storing the encrypted invitation key (`IKey`) in the sig chain (so any team admin could decrypt and reinvite), we avoid this by instead writing the public key of an EdDSA keypair in the sigchain, where we derive the secret key from the streched `IKey`. Only the admin that generates the `IKey` and the recipient of the invite ever have access to the `IKey` in this version.","created_at":"2018-01-25T22:36:48Z","updated_at":"2018-02-05T15:23:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1a8019222e69531ef3be2c7b7c67b7be6d7ddbd0","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"milestone":null,"commits_url":"https://api.github.com/repos/keybase/client/pulls/10333/commits","review_comments_url":"https://api.github.com/repos/keybase/client/pulls/10333/comments","review_comment_url":"https://api.github.com/repos/keybase/client/pulls/comments{/number}","comments_url":"https://api.github.com/repos/keybase/client/issues/10333/comments","statuses_url":"https://api.github.com/repos/keybase/client/statuses/bb5b751c43c07f5375fb8ca245129740a4904f1e","head":{"label":"keybase:seitan-v2","ref":"seitan-v2","sha":"bb5b751c43c07f5375fb8ca245129740a4904f1e","user":{"login":"keybase","id":5400834,"avatar_url":"https://avatars2.githubusercontent.com/u/5400834?v=4","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"repo":{"id":29099181,"name":"client","full_name":"keybase/client","owner":{"login":"keybase","id":5400834,"avatar_url":"https://avatars2.githubusercontent.com/u/5400834?v=4","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/keybase/client","description":"Keybase Go Library, Client, Service, OS X, iOS, Android, Electron","fork":false,"url":"https://api.github.com/repos/keybase/client","forks_url":"https://api.github.com/repos/keybase/client/forks","keys_url":"https://api.github.com/repos/keybase/client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/keybase/client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/keybase/client/teams","hooks_url":"https://api.github.com/repos/keybase/client/hooks","issue_events_url":"https://api.github.com/repos/keybase/client/issues/events{/number}","events_url":"https://api.github.com/repos/keybase/client/events","assignees_url":"https://api.github.com/repos/keybase/client/assignees{/user}","branches_url":"https://api.github.com/repos/keybase/client/branches{/branch}","tags_url":"https://api.github.com/repos/keybase/client/tags","blobs_url":"https://api.github.com/repos/keybase/client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/keybase/client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/keybase/client/git/refs{/sha}","trees_url":"https://api.github.com/repos/keybase/client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/keybase/client/statuses/{sha}","languages_url":"https://api.github.com/repos/keybase/client/languages","stargazers_url":"https://api.github.com/repos/keybase/client/stargazers","contributors_url":"https://api.github.com/repos/keybase/client/contributors","subscribers_url":"https://api.github.com/repos/keybase/client/subscribers","subscription_url":"https://api.github.com/repos/keybase/client/subscription","commits_url":"https://api.github.com/repos/keybase/client/commits{/sha}","git_commits_url":"https://api.github.com/repos/keybase/client/git/commits{/sha}","comments_url":"https://api.github.com/repos/keybase/client/comments{/number}","issue_comment_url":"https://api.github.com/repos/keybase/client/issues/comments{/number}","contents_url":"https://api.github.com/repos/keybase/client/contents/{+path}","compare_url":"https://api.github.com/repos/keybase/client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/keybase/client/merges","archive_url":"https://api.github.com/repos/keybase/client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/keybase/client/downloads","issues_url":"https://api.github.com/repos/keybase/client/issues{/number}","pulls_url":"https://api.github.com/repos/keybase/client/pulls{/number}","milestones_url":"https://api.github.com/repos/keybase/client/milestones{/number}","notifications_url":"https://api.github.com/repos/keybase/client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/keybase/client/labels{/name}","releases_url":"https://api.github.com/repos/keybase/client/releases{/id}","deployments_url":"https://api.github.com/repos/keybase/client/deployments","created_at":"2015-01-11T17:17:12Z","updated_at":"2018-02-05T15:09:37Z","pushed_at":"2018-02-05T14:32:31Z","git_url":"git://github.com/keybase/client.git","ssh_url":"git@github.com:keybase/client.git","clone_url":"https://github.com/keybase/client.git","svn_url":"https://github.com/keybase/client","homepage":"","size":220866,"stargazers_count":3189,"watchers_count":3189,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":301,"mirror_url":null,"archived":false,"open_issues_count":1816,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":301,"open_issues":1816,"watchers":3189,"default_branch":"master"}},"base":{"label":"keybase:master","ref":"master","sha":"ca12a55f7707bf44742fdc57390262f142cf67c6","user":{"login":"keybase","id":5400834,"avatar_url":"https://avatars2.githubusercontent.com/u/5400834?v=4","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"repo":{"id":29099181,"name":"client","full_name":"keybase/client","owner":{"login":"keybase","id":5400834,"avatar_url":"https://avatars2.githubusercontent.com/u/5400834?v=4","gravatar_id":"","url":"https://api.github.com/users/keybase","html_url":"https://github.com/keybase","followers_url":"https://api.github.com/users/keybase/followers","following_url":"https://api.github.com/users/keybase/following{/other_user}","gists_url":"https://api.github.com/users/keybase/gists{/gist_id}","starred_url":"https://api.github.com/users/keybase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/keybase/subscriptions","organizations_url":"https://api.github.com/users/keybase/orgs","repos_url":"https://api.github.com/users/keybase/repos","events_url":"https://api.github.com/users/keybase/events{/privacy}","received_events_url":"https://api.github.com/users/keybase/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/keybase/client","description":"Keybase Go Library, Client, Service, OS X, iOS, Android, Electron","fork":false,"url":"https://api.github.com/repos/keybase/client","forks_url":"https://api.github.com/repos/keybase/client/forks","keys_url":"https://api.github.com/repos/keybase/client/keys{/key_id}","collaborators_url":"https://api.github.com/repos/keybase/client/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/keybase/client/teams","hooks_url":"https://api.github.com/repos/keybase/client/hooks","issue_events_url":"https://api.github.com/repos/keybase/client/issues/events{/number}","events_url":"https://api.github.com/repos/keybase/client/events","assignees_url":"https://api.github.com/repos/keybase/client/assignees{/user}","branches_url":"https://api.github.com/repos/keybase/client/branches{/branch}","tags_url":"https://api.github.com/repos/keybase/client/tags","blobs_url":"https://api.github.com/repos/keybase/client/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/keybase/client/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/keybase/client/git/refs{/sha}","trees_url":"https://api.github.com/repos/keybase/client/git/trees{/sha}","statuses_url":"https://api.github.com/repos/keybase/client/statuses/{sha}","languages_url":"https://api.github.com/repos/keybase/client/languages","stargazers_url":"https://api.github.com/repos/keybase/client/stargazers","contributors_url":"https://api.github.com/repos/keybase/client/contributors","subscribers_url":"https://api.github.com/repos/keybase/client/subscribers","subscription_url":"https://api.github.com/repos/keybase/client/subscription","commits_url":"https://api.github.com/repos/keybase/client/commits{/sha}","git_commits_url":"https://api.github.com/repos/keybase/client/git/commits{/sha}","comments_url":"https://api.github.com/repos/keybase/client/comments{/number}","issue_comment_url":"https://api.github.com/repos/keybase/client/issues/comments{/number}","contents_url":"https://api.github.com/repos/keybase/client/contents/{+path}","compare_url":"https://api.github.com/repos/keybase/client/compare/{base}...{head}","merges_url":"https://api.github.com/repos/keybase/client/merges","archive_url":"https://api.github.com/repos/keybase/client/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/keybase/client/downloads","issues_url":"https://api.github.com/repos/keybase/client/issues{/number}","pulls_url":"https://api.github.com/repos/keybase/client/pulls{/number}","milestones_url":"https://api.github.com/repos/keybase/client/milestones{/number}","notifications_url":"https://api.github.com/repos/keybase/client/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/keybase/client/labels{/name}","releases_url":"https://api.github.com/repos/keybase/client/releases{/id}","deployments_url":"https://api.github.com/repos/keybase/client/deployments","created_at":"2015-01-11T17:17:12Z","updated_at":"2018-02-05T15:09:37Z","pushed_at":"2018-02-05T14:32:31Z","git_url":"git://github.com/keybase/client.git","ssh_url":"git@github.com:keybase/client.git","clone_url":"https://github.com/keybase/client.git","svn_url":"https://github.com/keybase/client","homepage":"","size":220866,"stargazers_count":3189,"watchers_count":3189,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":301,"mirror_url":null,"archived":false,"open_issues_count":1816,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":301,"open_issues":1816,"watchers":3189,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/keybase/client/pulls/10333"},"html":{"href":"https://github.com/keybase/client/pull/10333"},"issue":{"href":"https://api.github.com/repos/keybase/client/issues/10333"},"comments":{"href":"https://api.github.com/repos/keybase/client/issues/10333/comments"},"review_comments":{"href":"https://api.github.com/repos/keybase/client/pulls/10333/comments"},"review_comment":{"href":"https://api.github.com/repos/keybase/client/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/keybase/client/pulls/10333/commits"},"statuses":{"href":"https://api.github.com/repos/keybase/client/statuses/bb5b751c43c07f5375fb8ca245129740a4904f1e"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"joshblum"}}