github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/chain_test.go (about)

     1  package teams
     2  
     3  import (
     4  	"encoding/json"
     5  	"testing"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/davecgh/go-spew/spew"
    10  	"github.com/keybase/client/go/kbtest"
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/keybase/go-codec/codec"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  // A chain with a stubbed link
    18  // Output of `rotate_root_team_key` test in team_integration.iced
    19  const teamChain1 = `
    20  {"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgewPUgFaXvAjHWBC4BnTzSdMT3izYy89VX+4rjh2NlMkKp3BheWxvYWTEJ5UCAcDEIL95/rs8CFK3VSp4W4nYmxAdatMKJf+BB1xWH3eB/K7vIaNzaWfEQAez3e1PlPXJUg2vITA8/ZKeIKBp67DQQaNSKchzpPaMN8BFtvlY2qQStmk5Jn9mcN7m5x7xGBvRLksLkz4fOQSoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"01207b03d4805697bc08c75810b80674f349d313de2cd8cbcf555fee2b8e1d8d94c90a\",\"host\":\"keybase.io\",\"kid\":\"01207b03d4805697bc08c75810b80674f349d313de2cd8cbcf555fee2b8e1d8d94c90a\",\"uid\":\"5bf82de4331b50b32cbbcfeadc2f3119\",\"username\":\"d_af8eac8c\"},\"team\":{\"id\":\"64d27654bef64bdb3d78d84f186c4224\",\"members\":{\"admin\":[\"53e315afb4b419931b0a6a1eaa09e219\"],\"owner\":[\"5bf82de4331b50b32cbbcfeadc2f3119\"],\"reader\":[\"13e18aeafa4df6c94bf6af7d7bb98d19\"],\"writer\":[\"4bf92804c02fb7d2cd36a6d420d6f619\"]},\"name\":\"t_9d6d1e37\",\"per_team_key\":{\"encryption_kid\":\"0121bf2085a5f1b4f8e0ad5095fb29ae65f7e52a4fa5d9bc90757515c7dd860767020a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgYr3LeU54Mu4AdjeZ3bG+7c0yEL51p2dfHxneCIxTVPEKp3BheWxvYWTFA3R7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwN2IwM2Q0ODA1Njk3YmMwOGM3NTgxMGI4MDY3NGYzNDlkMzEzZGUyY2Q4Y2JjZjU1NWZlZTJiOGUxZDhkOTRjOTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwN2IwM2Q0ODA1Njk3YmMwOGM3NTgxMGI4MDY3NGYzNDlkMzEzZGUyY2Q4Y2JjZjU1NWZlZTJiOGUxZDhkOTRjOTBhIiwidWlkIjoiNWJmODJkZTQzMzFiNTBiMzJjYmJjZmVhZGMyZjMxMTkiLCJ1c2VybmFtZSI6ImRfYWY4ZWFjOGMifSwidGVhbSI6eyJpZCI6IjY0ZDI3NjU0YmVmNjRiZGIzZDc4ZDg0ZjE4NmM0MjI0IiwibWVtYmVycyI6eyJhZG1pbiI6WyI1M2UzMTVhZmI0YjQxOTkzMWIwYTZhMWVhYTA5ZTIxOSJdLCJvd25lciI6WyI1YmY4MmRlNDMzMWI1MGIzMmNiYmNmZWFkYzJmMzExOSJdLCJyZWFkZXIiOlsiMTNlMThhZWFmYTRkZjZjOTRiZjZhZjdkN2JiOThkMTkiXSwid3JpdGVyIjpbIjRiZjkyODA0YzAyZmI3ZDJjZDM2YTZkNDIwZDZmNjE5Il19LCJuYW1lIjoidF85ZDZkMWUzNyIsInBlcl90ZWFtX2tleSI6eyJlbmNyeXB0aW9uX2tpZCI6IjAxMjFiZjIwODVhNWYxYjRmOGUwYWQ1MDk1ZmIyOWFlNjVmN2U1MmE0ZmE1ZDliYzkwNzU3NTE1YzdkZDg2MDc2NzAyMGEiLCJnZW5lcmF0aW9uIjoxLCJyZXZlcnNlX3NpZyI6bnVsbCwic2lnbmluZ19raWQiOiIwMTIwNjJiZGNiNzk0ZTc4MzJlZTAwNzYzNzk5ZGRiMWJlZWRjZDMyMTBiZTc1YTc2NzVmMWYxOWRlMDg4YzUzNTRmMTBhIn19LCJ0eXBlIjoidGVhbS5yb290IiwidmVyc2lvbiI6Mn0sImN0aW1lIjoxNDk3MjM4NTMyLCJleHBpcmVfaW4iOjE1NzY4MDAwMCwicHJldiI6bnVsbCwic2VxX3R5cGUiOjMsInNlcW5vIjoxLCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQLrzfIl+c/rDlaTL9hW5emLJSJOoyhWw0gKnShh4v5FzX0tunexOId0U87etEgT1P+uJ6KYCSQTh8ZdCmDWJ7Q6oc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==\",\"signing_kid\":\"012062bdcb794e7832ee00763799ddb1beedcd3210be75a7675f1f19de088c5354f10a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1497238532,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"5bf82de4331b50b32cbbcfeadc2f3119"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgewPUgFaXvAjHWBC4BnTzSdMT3izYy89VX+4rjh2NlMkKp3BheWxvYWTESJUCAsQgYst2GtNGo9KL4e7RB8NVSKLdb63pIZo4WRhB9i1YbP7EICPQdqUmdeZU/gRJXd5+gUP8HSxtn4xMeZ7lssS3Pm+8IqNzaWfEQHkBp46skYqz62rMjoxZGq4HVjhJHCS4zYjmrMDhQQrl3fu76HeRqTeuLWCh0741OyvwXTjGNY7oCJiT5YvZSw+oc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","version":2,"uid":"5bf82de4331b50b32cbbcfeadc2f3119"},{"seqno":3,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgTqfhtGmmOGhixMw9YsHIrmrk0txnZBM4A/hqS1gWbzUKp3BheWxvYWTESJUCA8Qgg2TPT1Vbq+1yiOlYEFHqQYcccB4W6bevJFRK1jc1ov7EIHbADJMePlGQ1+JCJe9AQiftKeKwAIKLYLC1pPvcWyZmJKNzaWfEQPNVaBljU0mSO/27FfQDZiNaNYfbZ+lG1QF2WaOoUBgtChMxEek+3jKWTGkfWSvjL+MynM8ve+egRteBY8jhoQioc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"01204ea7e1b469a6386862c4cc3d62c1c8ae6ae4d2dc6764133803f86a4b58166f350a\",\"host\":\"keybase.io\",\"kid\":\"01204ea7e1b469a6386862c4cc3d62c1c8ae6ae4d2dc6764133803f86a4b58166f350a\",\"uid\":\"4bf92804c02fb7d2cd36a6d420d6f619\",\"username\":\"b_7804991a\"},\"team\":{\"id\":\"64d27654bef64bdb3d78d84f186c4224\",\"per_team_key\":{\"encryption_kid\":\"0121e2511cbfb0418187a8e19183a1cd92637bc83fe116d1eb8984f52394495b5f120a\",\"generation\":2,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEggC9V7V4U9hxzCHG16Z1jcFT/f1ugwbLMUrFU47r4CgAKp3BheWxvYWTFAuJ7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwNGVhN2UxYjQ2OWE2Mzg2ODYyYzRjYzNkNjJjMWM4YWU2YWU0ZDJkYzY3NjQxMzM4MDNmODZhNGI1ODE2NmYzNTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwNGVhN2UxYjQ2OWE2Mzg2ODYyYzRjYzNkNjJjMWM4YWU2YWU0ZDJkYzY3NjQxMzM4MDNmODZhNGI1ODE2NmYzNTBhIiwidWlkIjoiNGJmOTI4MDRjMDJmYjdkMmNkMzZhNmQ0MjBkNmY2MTkiLCJ1c2VybmFtZSI6ImJfNzgwNDk5MWEifSwidGVhbSI6eyJpZCI6IjY0ZDI3NjU0YmVmNjRiZGIzZDc4ZDg0ZjE4NmM0MjI0IiwicGVyX3RlYW1fa2V5Ijp7ImVuY3J5cHRpb25fa2lkIjoiMDEyMWUyNTExY2JmYjA0MTgxODdhOGUxOTE4M2ExY2Q5MjYzN2JjODNmZTExNmQxZWI4OTg0ZjUyMzk0NDk1YjVmMTIwYSIsImdlbmVyYXRpb24iOjIsInJldmVyc2Vfc2lnIjpudWxsLCJzaWduaW5nX2tpZCI6IjAxMjA4MDJmNTVlZDVlMTRmNjFjNzMwODcxYjVlOTlkNjM3MDU0ZmY3ZjViYTBjMWIyY2M1MmIxNTRlM2JhZjgwYTAwMGEifX0sInR5cGUiOiJ0ZWFtLnJvdGF0ZV9rZXkiLCJ2ZXJzaW9uIjoyfSwiY3RpbWUiOjE0OTcyMzg1MzUsImV4cGlyZV9pbiI6MTU3NjgwMDAwLCJwcmV2IjoiODM2NGNmNGY1NTViYWJlZDcyODhlOTU4MTA1MWVhNDE4NzFjNzAxZTE2ZTliN2FmMjQ1NDRhZDYzNzM1YTJmZSIsInNlcV90eXBlIjozLCJzZXFubyI6MywidGFnIjoic2lnbmF0dXJlIn2jc2lnxECfcafw2CoIzFKtmN2nt3A28wYS7clrmEZvjLEziNmoWy525gvyxJEHiENfxQ5kt9Uxb0cCDChlktHvz23my6QAqHNpZ190eXBlIKN0YWfNAgKndmVyc2lvbgE=\",\"signing_kid\":\"0120802f55ed5e14f61c730871b5e99d637054ff7f5ba0c1b2cc52b154e3baf80a000a\"}},\"type\":\"team.rotate_key\",\"version\":2},\"ctime\":1497238535,\"expire_in\":157680000,\"prev\":\"8364cf4f555babed7288e9581051ea41871c701e16e9b7af24544ad63735a2fe\",\"seq_type\":3,\"seqno\":3,\"tag\":\"signature\"}","version":2,"uid":"4bf92804c02fb7d2cd36a6d420d6f619"}],"box":{"nonce":"5VPBQypqrcLiuW1i6fVROxmuBQUAAAAD","sender_kid":"012112f29aa42e14053a057a790a198a5b7fb25512c8458b4b32d7bbd04c0d52093b0a","generation":2,"ctext":"HgbVY5cswOvxu9PMOP75NdYqftGtgenRABKtjHgervLK6/oaF1vTW2U9vt+0JixD","per_user_key_seqno":3},"prevs":{"2":"9301c418e553c1432a6aadc2e2b96d62e9f5513b19ae050500000000c4304e966d60d2ccee5433fa1cf08f11de02b0d4e749798925d925896edc6c0b12288a975db3b29f6efed165b38775b64d42"},"reader_key_masks":[{"mask":"gKBbFV3J3L0j/gFtruyCKpZHf7Y837Q2ezFtrAK6xIE=","application":1,"generation":1},{"mask":"ZYdXI0jfXscYwgXO3J3A1T9YRJ+GlkNvtEZJ4nvmKbk=","application":2,"generation":1},{"mask":"Dq4iXhUs9BxX1DHYP7wE/vFOpG4SABwzHZRQeavnKjM=","application":1,"generation":2},{"mask":"AEedcZX7wZyvyAOyc9EQINr3MyqbKMKXLfZVHHZqz7U=","application":2,"generation":2}],"id":"64d27654bef64bdb3d78d84f186c4224","name":{"parts":["t_9d6d1e37"]},"csrf_token":"lgHZIDRiZjkyODA0YzAyZmI3ZDJjZDM2YTZkNDIwZDZmNjE5zlk+C/7OAAFRgMDEIFLYZSdoOin9JRKgyjN8z/JMVQ4Az3O1ZcUyT43DTlXV"}
    21  `
    22  
    23  // A chain with a change_membership link, generated via: `change_membership_promote_to_writer_happy_path`
    24  const teamChain2 = `
    25  {"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg0n4BukVK2MAN0FR3OKg2LjyIRb927JAVdNeKFKxDRTsKp3BheWxvYWTEJ5UCAcDEIKkpvmNu1RlxqBwYtqk4eG/jKv7KD/GllQ23k/Dd6VB1IaNzaWfEQCNHcFQwf9uDBjhzyXDydfUn/7QK1MgC4T2xW/4hywf9vXdVLJ3sPWb+gvk2ZoPCdiwmiAl3CCMiUIuaZrEIawSoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"host\":\"keybase.io\",\"kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"uid\":\"99759da4f968b16121ece44652f01a19\",\"username\":\"d_6d4e925d\"},\"team\":{\"id\":\"5d2c9db17c2309bf818ceefece77b624\",\"members\":{\"admin\":[\"b720a648e02b99c10d50de0c4f265419\"],\"owner\":[\"99759da4f968b16121ece44652f01a19\"],\"reader\":[\"c8f463c79c83fec675c398b6aa3fa719\"],\"writer\":[\"921f0e1f2632277cc1fa6600e0906819\"]},\"name\":\"t_bfaadb41\",\"per_team_key\":{\"encryption_kid\":\"01218ca00b08b4ee5729d957cf14155098b74199588bb5eee778ad1eae58bce26c370a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEgiyRSCOiVHN56vT9eusVXlCTlHtCVH+FKyKeZbJzPiuUKp3BheWxvYWTFA3R7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwZDI3ZTAxYmE0NTRhZDhjMDBkZDA1NDc3MzhhODM2MmUzYzg4NDViZjc2ZWM5MDE1NzRkNzhhMTRhYzQzNDUzYjBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwZDI3ZTAxYmE0NTRhZDhjMDBkZDA1NDc3MzhhODM2MmUzYzg4NDViZjc2ZWM5MDE1NzRkNzhhMTRhYzQzNDUzYjBhIiwidWlkIjoiOTk3NTlkYTRmOTY4YjE2MTIxZWNlNDQ2NTJmMDFhMTkiLCJ1c2VybmFtZSI6ImRfNmQ0ZTkyNWQifSwidGVhbSI6eyJpZCI6IjVkMmM5ZGIxN2MyMzA5YmY4MThjZWVmZWNlNzdiNjI0IiwibWVtYmVycyI6eyJhZG1pbiI6WyJiNzIwYTY0OGUwMmI5OWMxMGQ1MGRlMGM0ZjI2NTQxOSJdLCJvd25lciI6WyI5OTc1OWRhNGY5NjhiMTYxMjFlY2U0NDY1MmYwMWExOSJdLCJyZWFkZXIiOlsiYzhmNDYzYzc5YzgzZmVjNjc1YzM5OGI2YWEzZmE3MTkiXSwid3JpdGVyIjpbIjkyMWYwZTFmMjYzMjI3N2NjMWZhNjYwMGUwOTA2ODE5Il19LCJuYW1lIjoidF9iZmFhZGI0MSIsInBlcl90ZWFtX2tleSI6eyJlbmNyeXB0aW9uX2tpZCI6IjAxMjE4Y2EwMGIwOGI0ZWU1NzI5ZDk1N2NmMTQxNTUwOThiNzQxOTk1ODhiYjVlZWU3NzhhZDFlYWU1OGJjZTI2YzM3MGEiLCJnZW5lcmF0aW9uIjoxLCJyZXZlcnNlX3NpZyI6bnVsbCwic2lnbmluZ19raWQiOiIwMTIwOGIyNDUyMDhlODk1MWNkZTdhYmQzZjVlYmFjNTU3OTQyNGU1MWVkMDk1MWZlMTRhYzhhNzk5NmM5Y2NmOGFlNTBhIn19LCJ0eXBlIjoidGVhbS5yb290IiwidmVyc2lvbiI6Mn0sImN0aW1lIjoxNDk3MjM5NTY2LCJleHBpcmVfaW4iOjE1NzY4MDAwMCwicHJldiI6bnVsbCwic2VxX3R5cGUiOjMsInNlcW5vIjoxLCJ0YWciOiJzaWduYXR1cmUifaNzaWfEQIhIqjwqQQFY8WglSLtvZu1hpncnMutA/jLaFmQJWcjdoMilr4ttLg3wlxKm6m+zWJC0Y9tiYBeHqGLYj5Mmkgaoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==\",\"signing_kid\":\"01208b245208e8951cde7abd3f5ebac5579424e51ed0951fe14ac8a7996c9ccf8ae50a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1497239566,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"99759da4f968b16121ece44652f01a19"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg0n4BukVK2MAN0FR3OKg2LjyIRb927JAVdNeKFKxDRTsKp3BheWxvYWTESJUCAsQgaCisMgGb2MAyfqV80hthgsO25NQIAYK7ARn5pjCDP1HEIKj3ZEpkyhQYH2mYtiHiXQVe7V5D4qgNwupJ5Nr/2nhcI6NzaWfEQNKql5dvPsQJK+pZKVGiLWS723t9SgaaDFx6NicXJzOM0VnLHKnql50wUcY/KsJOCqUIpKvJmNj6ogbwN/ljTwaoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"host\":\"keybase.io\",\"kid\":\"0120d27e01ba454ad8c00dd0547738a8362e3c8845bf76ec901574d78a14ac43453b0a\",\"uid\":\"99759da4f968b16121ece44652f01a19\",\"username\":\"d_6d4e925d\"},\"team\":{\"id\":\"5d2c9db17c2309bf818ceefece77b624\",\"members\":{\"writer\":[\"c8f463c79c83fec675c398b6aa3fa719\"]}},\"type\":\"team.change_membership\",\"version\":2},\"ctime\":1497239567,\"expire_in\":157680000,\"prev\":\"6828ac32019bd8c0327ea57cd21b6182c3b6e4d4080182bb0119f9a630833f51\",\"seq_type\":3,\"seqno\":2,\"tag\":\"signature\"}","version":2,"uid":"99759da4f968b16121ece44652f01a19"}],"box":{"nonce":"hdDTz9Scb6dWbe+BZsyZaXx76aQAAAAB","sender_kid":"0121a4f15b1009430ff69224c0c659eba66d61ca743b0d661a79075c8ca63a6e535d0a","generation":1,"ctext":"4Dz4i3Wzdj/BdH/kYCXvFnl1XKCqhxc58Z53j9VDloy/KG2ZzH204Sw5Q1xkzFkV","per_user_key_seqno":3},"prevs":{},"reader_key_masks":[{"mask":"tewSORjnVoyuHKR6ztsND+MNP2Pp9skEEEYmMBIQ5cY=","application":1,"generation":1},{"mask":"x7ZvY+WfK6WaJOCulxfOpdLgBEyuzSc8KgyIQGxT2uQ=","application":2,"generation":1}],"id":"5d2c9db17c2309bf818ceefece77b624","name":{"parts":["t_bfaadb41"]},"csrf_token":"lgHZIDk5NzU5ZGE0Zjk2OGIxNjEyMWVjZTQ0NjUyZjAxYTE5zlk+EA3OAAFRgMDEIKCIaEA5GV5wng89rpM0UlLiqxcOyNIVk8KdsEjQpmAm"}
    26  `
    27  
    28  // A chain with an invite and a cancelation, as generated via: `cancel_invite_happy_path`
    29  const teamChainWithInvites = `
    30  {"status":{"code":0,"name":"OK"},"chain":[{"seqno":1,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTEJ5UCAcDEIEi+9D4UalDtumiKbRRbMIx6b3w7pJ/wE/+P50FWPLiwIaNzaWfEQGBtlFet2J8qB9rUjGAFU8w8WBECB2zn5Tr5g1PbXJK3q6uG0GPWbUFrsY8DCopgKcucthrz0/58ehnwpvbwjAyoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"8d60b173eadcb63f20fd6089fed7c1f4031b805e3892ef6c4be432737e6470ff9d1dbd48d6e81d33dd6480b56b6451775c4d266ba2f12bacc00a758f95614844\",\"hash_meta\":\"98d75dd2b3b27dba1599babdfdbd0b7cac312c2c9b0d96ad438277bef5454566\",\"seqno\":332458},\"team\":{\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"members\":{\"owner\":[\"934b8105dc1bd94d8be109b08ef5c119\"],\"writer\":[\"d71179f6b33171b72695ee23a44ecc19\"]},\"name\":\"t_efc95e26\",\"per_team_key\":{\"encryption_kid\":\"0121c41ecfba3ec588c029a096adc78f9419c935059c8a694ac30c0c7f30b499485e0a\",\"generation\":1,\"reverse_sig\":\"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEg/jl9IghbR8CHSTUp2sYPIBLOEMa4b9enId/u5nD/OMoKp3BheWxvYWTFBCN7ImJvZHkiOnsia2V5Ijp7ImVsZGVzdF9raWQiOiIwMTIwOTQwNzVjNjhkM2MwZTNjNjdiYTkzMDlmYzQzYjkyNWFhNjg1ODZjY2Y2YzI3ODczM2Y4NWMwN2Y5YzYyZjFjMTBhIiwiaG9zdCI6ImtleWJhc2UuaW8iLCJraWQiOiIwMTIwOTQwNzVjNjhkM2MwZTNjNjdiYTkzMDlmYzQzYjkyNWFhNjg1ODZjY2Y2YzI3ODczM2Y4NWMwN2Y5YzYyZjFjMTBhIiwidWlkIjoiOTM0YjgxMDVkYzFiZDk0ZDhiZTEwOWIwOGVmNWMxMTkiLCJ1c2VybmFtZSI6ImE2NjcxNDUzMSJ9LCJtZXJrbGVfcm9vdCI6eyJjdGltZSI6MTQ5OTM1ODY4OCwiaGFzaCI6IjhkNjBiMTczZWFkY2I2M2YyMGZkNjA4OWZlZDdjMWY0MDMxYjgwNWUzODkyZWY2YzRiZTQzMjczN2U2NDcwZmY5ZDFkYmQ0OGQ2ZTgxZDMzZGQ2NDgwYjU2YjY0NTE3NzVjNGQyNjZiYTJmMTJiYWNjMDBhNzU4Zjk1NjE0ODQ0IiwiaGFzaF9tZXRhIjoiOThkNzVkZDJiM2IyN2RiYTE1OTliYWJkZmRiZDBiN2NhYzMxMmMyYzliMGQ5NmFkNDM4Mjc3YmVmNTQ1NDU2NiIsInNlcW5vIjozMzI0NTh9LCJ0ZWFtIjp7ImlkIjoiN2ViZTRkYmZlNDU4ZmRlOWE1ZTJmZmRjNGViOTZhMjQiLCJtZW1iZXJzIjp7Im93bmVyIjpbIjkzNGI4MTA1ZGMxYmQ5NGQ4YmUxMDliMDhlZjVjMTE5Il0sIndyaXRlciI6WyJkNzExNzlmNmIzMzE3MWI3MjY5NWVlMjNhNDRlY2MxOSJdfSwibmFtZSI6InRfZWZjOTVlMjYiLCJwZXJfdGVhbV9rZXkiOnsiZW5jcnlwdGlvbl9raWQiOiIwMTIxYzQxZWNmYmEzZWM1ODhjMDI5YTA5NmFkYzc4Zjk0MTljOTM1MDU5YzhhNjk0YWMzMGMwYzdmMzBiNDk5NDg1ZTBhIiwiZ2VuZXJhdGlvbiI6MSwicmV2ZXJzZV9zaWciOm51bGwsInNpZ25pbmdfa2lkIjoiMDEyMGZlMzk3ZDIyMDg1YjQ3YzA4NzQ5MzUyOWRhYzYwZjIwMTJjZTEwYzZiODZmZDdhNzIxZGZlZWU2NzBmZjM4Y2EwYSJ9fSwidHlwZSI6InRlYW0ucm9vdCIsInZlcnNpb24iOjJ9LCJjdGltZSI6MTQ5OTM1ODY4OCwiZXhwaXJlX2luIjoxNTc2ODAwMDAsInByZXYiOm51bGwsInNlcV90eXBlIjozLCJzZXFubyI6MSwidGFnIjoic2lnbmF0dXJlIn2jc2lnxEDB7QWbAjIcXgVqq6GnGXNK/IbwNMDq9EVTOfUGQYk63CSRL3zLKCmQh0s3qdPiT5SXNipSBXdX+pvp8/cK1wILqHNpZ190eXBlIKN0YWfNAgKndmVyc2lvbgE=\",\"signing_kid\":\"0120fe397d22085b47c087493529dac60f2012ce10c6b86fd7a721dfeee670ff38ca0a\"}},\"type\":\"team.root\",\"version\":2},\"ctime\":1499358688,\"expire_in\":157680000,\"prev\":null,\"seq_type\":3,\"seqno\":1,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"},{"seqno":2,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTESJUCAsQg7vTbqMWQF4drUC6qX8AT4D8QyRBuebus07XiQT9rdD/EIKpkXqyCDdvYFx8b3A0ROEjwmgS26msj5nVkjPPWpwrFKKNzaWfEQNVi98WQKl9JvPfQxAU8QtxIajxKgwUVwKMcpRoAlfViMXHizVl7EQbnPtAL7AfE26HGd2pVFzCJgyP19Smzpgqoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"1dab3ca40bb0eccc8561e20f356bdd4c46cca79e418ccc4280d59ebdac022180d547f8765b1f89fc650c535d65516c96b5e4fc811383b711df550af459dc8deb\",\"hash_meta\":\"96d350830b218ac64ad415c8d94f219a85813d451a6d69fd358badb353ac69b1\",\"seqno\":332460},\"team\":{\"admin\":{\"seq_type\":3,\"seqno\":1,\"team_id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\"},\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"invites\":{\"admin\":[{\"id\":\"6acd369ec6f5649c4ef83c8753b3aa27\",\"name\":\"u_8114060fcef4\",\"type\":\"twitter\"}],\"reader\":[{\"id\":\"117b4f1d1048042cb67e204c84d07927\",\"name\":\"u_8114060fcef4\",\"type\":\"reddit\"}],\"writer\":[{\"id\":\"4f66ee0fa60ecb10b9f59ff6b7157527\",\"name\":\"max+8114060fcef4@keyba.se\",\"type\":\"email\"},{\"id\":\"b90e024124ddd80870759bae42143227\",\"name\":\"u_8114060fcef4\",\"type\":\"rooter\"}]}},\"type\":\"team.invite\",\"version\":2},\"ctime\":1499358688,\"expire_in\":157680000,\"prev\":\"eef4dba8c59017876b502eaa5fc013e03f10c9106e79bbacd3b5e2413f6b743f\",\"seq_type\":3,\"seqno\":2,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"},{"seqno":3,"sig":"g6Rib2R5hqhkZXRhY2hlZMOpaGFzaF90eXBlCqNrZXnEIwEglAdcaNPA48Z7qTCfxDuSWqaFhsz2wnhzP4XAf5xi8cEKp3BheWxvYWTESJUCA8Qg0sz2c4Djy6V9F8p/maoKpNc4XLhuY+eU585IPs3mOdrEIMS//+b6ohtqz57toWOEF4HKUAHyBQYvGAV9h9NfaszqKKNzaWfEQLf3O7lXJFVK/fetuCPuYqBUeITs7aVBmOVoZm02xRNzNw940jZRAqFZrX7c/6lqqC/ARl+5iLdnbwkErRjcWAmoc2lnX3R5cGUgo3RhZ80CAqd2ZXJzaW9uAQ==","payload_json":"{\"body\":{\"key\":{\"eldest_kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"host\":\"keybase.io\",\"kid\":\"012094075c68d3c0e3c67ba9309fc43b925aa68586ccf6c278733f85c07f9c62f1c10a\",\"uid\":\"934b8105dc1bd94d8be109b08ef5c119\",\"username\":\"a66714531\"},\"merkle_root\":{\"ctime\":1499358688,\"hash\":\"fdc2a6eb4d685e8b8048946eaf62fe1b5caddb84bcbb1207c1ccf5e65f1d656f5feb4cfc467c121966d6adb128dd8c50b92b64a523f08f8b566b8600df7d9a35\",\"hash_meta\":\"d5818082cee4e8add8081fb605c9cb06cbdc6c4343085d07f9b89e61d2e671fa\",\"seqno\":332461},\"team\":{\"admin\":{\"seq_type\":3,\"seqno\":1,\"team_id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\"},\"id\":\"7ebe4dbfe458fde9a5e2ffdc4eb96a24\",\"invites\":{\"cancel\":[\"117b4f1d1048042cb67e204c84d07927\"]}},\"type\":\"team.invite\",\"version\":2},\"ctime\":1499358689,\"expire_in\":157680000,\"prev\":\"d2ccf67380e3cba57d17ca7f99aa0aa4d7385cb86e63e794e7ce483ecde639da\",\"seq_type\":3,\"seqno\":3,\"tag\":\"signature\"}","version":2,"uid":"934b8105dc1bd94d8be109b08ef5c119"}],"box":{"nonce":"u0oZgbBS5lMOVM9po9vfjwGoxAgAAAAB","sender_kid":"0121d11a0dcb4e0fb545087ef58ff0366ed348f391c9d823ad5ed6616895cc9911030a","generation":1,"ctext":"c9wOION/iDRyTkaN1oTmrSsr/gAgIdULzPqroisEEWU3aCjkGBuu27NqGfXIAe5T","per_user_key_seqno":3},"prevs":{},"reader_key_masks":[{"mask":"2UljdLeivosnFh5Zx0HhPMNYD2n4kz4+cKisRp83q+M=","application":1,"generation":1},{"mask":"gIQztS2j/k26inXIgguOjuk3/AaGfc2thu5pDhtgBvw=","application":2,"generation":1},{"mask":"XGmeo2qgtH62ihZ6hTfmlvGm1PiIkpyyvicUt8VSepk=","application":3,"generation":1}],"id":"7ebe4dbfe458fde9a5e2ffdc4eb96a24","name":{"parts":["t_efc95e26"]},"csrf_token":"lgHZIDkzNGI4MTA1ZGMxYmQ5NGQ4YmUxMDliMDhlZjVjMTE5zlleZeDOAAFRgMDEIGUrg9Ioa5XufDOGCznLyJGlYgcc0d9GzaudNoIqDU8S"}
    31  `
    32  
    33  type DeconstructJig struct {
    34  	Chain []json.RawMessage `json:"chain"`
    35  }
    36  
    37  func TestTeamSigChainParse(t *testing.T) {
    38  	tc := SetupTest(t, "test_team_chains", 1)
    39  	defer tc.Cleanup()
    40  
    41  	var jig DeconstructJig
    42  	err := json.Unmarshal([]byte(teamChain1), &jig)
    43  	require.NoError(t, err)
    44  
    45  	for _, link := range jig.Chain {
    46  		// t.Logf("link: %v", string(link))
    47  
    48  		chainLink, err := ParseTeamChainLink(string(link))
    49  		require.NoError(t, err)
    50  
    51  		t.Logf("chainLink: %v", spew.Sdump(chainLink))
    52  
    53  		if len(chainLink.Payload) > 0 {
    54  			payload, err := chainLink.UnmarshalPayload()
    55  			require.NoError(t, err)
    56  			t.Logf("payload: %v", spew.Sdump(payload))
    57  		} else {
    58  			t.Logf("payload stubbed")
    59  		}
    60  	}
    61  }
    62  
    63  func assertHighSeqForTeam(t *testing.T, tc libkb.TestContext, teamID *keybase1.TeamID, expected int) {
    64  	team, err := Load(context.TODO(), tc.G, keybase1.LoadTeamArg{
    65  		ID:          *teamID,
    66  		ForceRepoll: true,
    67  	})
    68  	require.NoError(t, err)
    69  	actual := int(team.chain().GetLatestHighSeqno())
    70  	require.Equal(t, expected, actual)
    71  }
    72  
    73  func TestTeamSigChainHighLinks(t *testing.T) {
    74  	tc := SetupTest(t, "team_sig_chain_high_links", 1)
    75  	defer tc.Cleanup()
    76  	ctx := context.TODO()
    77  
    78  	// Create some users. The owner is last so that it has the active session.
    79  	u2, err := kbtest.CreateAndSignupFakeUser("we", tc.G) // admin
    80  	require.NoError(t, err)
    81  	u3, err := kbtest.CreateAndSignupFakeUser("ji", tc.G) // non-admin
    82  	require.NoError(t, err)
    83  	u4, err := kbtest.CreateAndSignupFakeUser("botua", tc.G) // bot
    84  	require.NoError(t, err)
    85  	u5, err := kbtest.CreateAndSignupFakeUser("rua", tc.G) // restricted_bot
    86  	require.NoError(t, err)
    87  	u1, err := kbtest.CreateAndSignupFakeUser("je", tc.G) // owner
    88  	require.NoError(t, err)
    89  	t.Logf("create the team...")
    90  	// Create a team. This creates the first high link.
    91  	teamName := u1.Username + "t"
    92  	teamNameObj, err := keybase1.TeamNameFromString(teamName)
    93  	require.NoError(t, err)
    94  	teamID, err := CreateRootTeam(ctx, tc.G, teamName, keybase1.TeamSettings{})
    95  	require.NoError(t, err)
    96  	assertHighSeqForTeam(t, tc, teamID, 1)
    97  
    98  	t.Logf("adding new reader...")
    99  	// Adding a new reader is not a high link, so the lastest high seq won't change.
   100  	_, err = AddMember(ctx, tc.G, teamName, u3.Username, keybase1.TeamRole_READER, nil)
   101  	require.NoError(t, err)
   102  	assertHighSeqForTeam(t, tc, teamID, 1)
   103  
   104  	// Adding a new bot is not a high link, so the latest high seq won't
   105  	// change. Note adding a RESTRICTEDBOT results in two sigs being added, one
   106  	// for the membership addition and a second for bot_settings.
   107  	_, err = AddMember(ctx, tc.G, teamName, u4.Username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{})
   108  	require.NoError(t, err)
   109  	assertHighSeqForTeam(t, tc, teamID, 1)
   110  
   111  	_, err = AddMember(ctx, tc.G, teamName, u5.Username, keybase1.TeamRole_BOT, nil)
   112  	require.NoError(t, err)
   113  	assertHighSeqForTeam(t, tc, teamID, 1)
   114  
   115  	t.Logf("adding new admin...")
   116  	// Adding a new admin IS a high link, so we should jump to 6.
   117  	_, err = AddMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_ADMIN, nil)
   118  	require.NoError(t, err)
   119  	assertHighSeqForTeam(t, tc, teamID, 6)
   120  
   121  	t.Logf("promoting from admin to owner...")
   122  	// Promoting from admin to owner is a high link.
   123  	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_OWNER, nil)
   124  	require.NoError(t, err)
   125  	assertHighSeqForTeam(t, tc, teamID, 7)
   126  
   127  	t.Logf("demoting from owner to admin...")
   128  	// Demoting from owner to admin is a high link.
   129  	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_ADMIN, nil)
   130  	require.NoError(t, err)
   131  	assertHighSeqForTeam(t, tc, teamID, 8)
   132  
   133  	t.Logf("adding new subteam...")
   134  	// Creating a subteam is not a high link for the parent team
   135  	// but it is for the subteam. The link types are different
   136  	// "team.root" and "team.subteam_head" so we should check both teams.
   137  	sub := "sub"
   138  	subteamID, err := CreateSubteam(ctx, tc.G, sub, teamNameObj, keybase1.TeamRole_ADMIN)
   139  	require.NoError(t, err)
   140  	assertHighSeqForTeam(t, tc, subteamID, 1)
   141  	assertHighSeqForTeam(t, tc, teamID, 8)
   142  
   143  	t.Logf("adding new admin to subteam...")
   144  	// Adding an admin to the subteam is a high link for the subteam but not
   145  	// the parent. Primarily, we do this to verify that high links advance
   146  	// the same way on subteams (since there are places that default to a
   147  	// value of 1 for sequence number). It's overkill to test any more than
   148  	// just this on the subteam since it should work the same way.
   149  	_, err = AddMemberByID(ctx, tc.G, *subteamID, u3.Username, keybase1.TeamRole_ADMIN, nil, nil /* emailInviteMsg */)
   150  	require.NoError(t, err)
   151  	assertHighSeqForTeam(t, tc, subteamID, 2)
   152  	assertHighSeqForTeam(t, tc, teamID, 8)
   153  
   154  	t.Logf("demoting admin to writer...")
   155  	// Back to the root team... downgrading an admin IS a high link for the root team
   156  	// but it is not one for the subteam, because the subteam needs to care about it's
   157  	// parent's high links anyway.
   158  	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_WRITER, nil)
   159  	require.NoError(t, err)
   160  	assertHighSeqForTeam(t, tc, teamID, 10)
   161  	assertHighSeqForTeam(t, tc, subteamID, 2)
   162  
   163  	t.Logf("demoting admin...")
   164  	// Back to the root team... downgrading an admin IS a high link for the root team
   165  	// but it is not one for the subteam, because the subteam needs to care about it's
   166  	// parent's high links anyway.
   167  	err = EditMember(ctx, tc.G, teamName, u2.Username, keybase1.TeamRole_WRITER, nil)
   168  	require.NoError(t, err)
   169  	assertHighSeqForTeam(t, tc, teamID, 10)
   170  	assertHighSeqForTeam(t, tc, subteamID, 2)
   171  
   172  	t.Logf("rotating keys...")
   173  	// Rotated keys do not create high links.
   174  	err = RotateKeyVisible(ctx, tc.G, *teamID)
   175  	require.NoError(t, err)
   176  	assertHighSeqForTeam(t, tc, teamID, 10)
   177  	assertHighSeqForTeam(t, tc, subteamID, 2)
   178  
   179  	t.Logf("deleting subteam...")
   180  	// Deleting a subteam will not create a high link.
   181  	err = Delete(ctx, tc.G, &teamsUI{}, *subteamID)
   182  	require.NoError(t, err)
   183  	assertHighSeqForTeam(t, tc, teamID, 10)
   184  }
   185  
   186  func TestTeamSigChainPlay1(t *testing.T) {
   187  	tc := SetupTest(t, "test_team_chains", 1)
   188  	defer tc.Cleanup()
   189  
   190  	var jig DeconstructJig
   191  	err := json.Unmarshal([]byte(teamChain1), &jig)
   192  	require.NoError(t, err)
   193  
   194  	var chainLinks []SCChainLink
   195  	for i, link := range jig.Chain {
   196  		// t.Logf("link: %v", string(link))
   197  
   198  		chainLink, err := ParseTeamChainLink(string(link))
   199  		require.NoError(t, err)
   200  
   201  		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
   202  		chainLinks = append(chainLinks, chainLink)
   203  	}
   204  
   205  	consumer := NewUserVersion(keybase1.UID("4bf92804c02fb7d2cd36a6d420d6f619"), 1)
   206  	var state *TeamSigChainState
   207  	for _, cLink := range chainLinks {
   208  		link, err := unpackChainLink(&cLink)
   209  		require.NoError(t, err)
   210  		var signer *keybase1.UserVersion
   211  		if !link.isStubbed() {
   212  			// Assume the signing user has never reset.
   213  			signer = &keybase1.UserVersion{
   214  				Uid:         link.inner.Body.Key.UID,
   215  				EldestSeqno: keybase1.Seqno(1),
   216  			}
   217  		}
   218  		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
   219  		require.NoError(t, err)
   220  		state = &newState
   221  	}
   222  
   223  	// Check once before and after serializing and deserializing
   224  	mctx := libkb.NewMetaContextForTest(tc)
   225  	for i := 0; i < 2; i++ {
   226  		if i == 0 {
   227  			t.Logf("testing fresh")
   228  		} else {
   229  			t.Logf("testing serde")
   230  		}
   231  
   232  		require.Equal(t, "t_9d6d1e37", string(state.LatestLastNamePart()))
   233  		require.False(t, state.IsSubteam())
   234  		ptk, err := state.GetLatestPerTeamKey(mctx)
   235  		require.NoError(t, err)
   236  		require.Equal(t, keybase1.PerTeamKeyGeneration(2), ptk.Gen)
   237  		require.Equal(t, keybase1.Seqno(3), ptk.Seqno)
   238  		require.Equal(t, "0120802f55ed5e14f61c730871b5e99d637054ff7f5ba0c1b2cc52b154e3baf80a000a", string(ptk.SigKID))
   239  		require.Equal(t, "0121e2511cbfb0418187a8e19183a1cd92637bc83fe116d1eb8984f52394495b5f120a", string(ptk.EncKID))
   240  		require.Equal(t, keybase1.Seqno(3), state.GetLatestSeqno())
   241  		require.Equal(t, state.GetLatestHighSeqno(), keybase1.Seqno(1))
   242  		require.Equal(t, state.GetLatestHighLinkID(), keybase1.LinkID("62cb761ad346a3d28be1eed107c35548a2dd6fade9219a38591841f62d586cfe"))
   243  
   244  		checkRole := func(uid keybase1.UID, role keybase1.TeamRole) {
   245  			uv := NewUserVersion(uid, 1)
   246  			r, err := state.GetUserRole(uv)
   247  			require.NoError(t, err)
   248  			require.Equal(t, role, r)
   249  		}
   250  
   251  		checkRole("5bf82de4331b50b32cbbcfeadc2f3119", keybase1.TeamRole_OWNER)  // the "doug" user
   252  		checkRole("53e315afb4b419931b0a6a1eaa09e219", keybase1.TeamRole_ADMIN)  // the "charlie" user
   253  		checkRole("4bf92804c02fb7d2cd36a6d420d6f619", keybase1.TeamRole_WRITER) // the "bob" user
   254  		checkRole("13e18aeafa4df6c94bf6af7d7bb98d19", keybase1.TeamRole_READER) // the "alice" user
   255  		checkRole("popeye", keybase1.TeamRole_NONE)
   256  
   257  		linkIDProto := state.GetLatestLinkID()
   258  		require.Equal(t, "c94234a4855e47d5833a7f43b221ca5e5ccf9970465b77167a793366acf39b16", string(linkIDProto))
   259  		linkIDLibkb, err := libkb.ImportLinkID(linkIDProto)
   260  		require.NoError(t, err)
   261  		require.Equal(t, linkIDProto, linkIDLibkb.Export())
   262  
   263  		// Reserialize
   264  		bs, err := encode(state.inner)
   265  		require.NoError(t, err, "encode")
   266  		state = &TeamSigChainState{}
   267  		err = decode(bs, &state.inner)
   268  		require.NoError(t, err, "decode")
   269  	}
   270  }
   271  
   272  func TestTeamSigChainPlay2(t *testing.T) {
   273  	tc := SetupTest(t, "test_team_chains", 1)
   274  	defer tc.Cleanup()
   275  
   276  	var jig DeconstructJig
   277  	err := json.Unmarshal([]byte(teamChain2), &jig)
   278  	require.NoError(t, err)
   279  
   280  	var chainLinks []SCChainLink
   281  	for i, link := range jig.Chain {
   282  		// t.Logf("link: %v", string(link))
   283  
   284  		chainLink, err := ParseTeamChainLink(string(link))
   285  		require.NoError(t, err)
   286  
   287  		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
   288  		chainLinks = append(chainLinks, chainLink)
   289  	}
   290  
   291  	consumer := NewUserVersion("99759da4f968b16121ece44652f01a19", 1)
   292  	var state *TeamSigChainState
   293  	for _, cLink := range chainLinks {
   294  		link, err := unpackChainLink(&cLink)
   295  		require.NoError(t, err)
   296  		var signer *keybase1.UserVersion
   297  		if !link.isStubbed() {
   298  			// Assume the signing user has never reset.
   299  			signer = &keybase1.UserVersion{
   300  				Uid:         link.inner.Body.Key.UID,
   301  				EldestSeqno: keybase1.Seqno(1),
   302  			}
   303  		}
   304  		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
   305  		require.NoError(t, err)
   306  		state = &newState
   307  	}
   308  	require.NoError(t, err)
   309  
   310  	mctx := libkb.NewMetaContextForTest(tc)
   311  
   312  	// Check once before and after serializing and deserializing
   313  	for i := 0; i < 2; i++ {
   314  		require.Equal(t, "t_bfaadb41", string(state.LatestLastNamePart()))
   315  		require.False(t, state.IsSubteam())
   316  		ptk, err := state.GetLatestPerTeamKey(mctx)
   317  		require.NoError(t, err)
   318  		require.Equal(t, keybase1.PerTeamKeyGeneration(1), ptk.Gen)
   319  		require.Equal(t, keybase1.Seqno(1), ptk.Seqno)
   320  		require.Equal(t, "01208b245208e8951cde7abd3f5ebac5579424e51ed0951fe14ac8a7996c9ccf8ae50a", string(ptk.SigKID))
   321  		require.Equal(t, "01218ca00b08b4ee5729d957cf14155098b74199588bb5eee778ad1eae58bce26c370a", string(ptk.EncKID))
   322  		require.Equal(t, keybase1.Seqno(2), state.GetLatestSeqno())
   323  		require.Equal(t, state.GetLatestHighSeqno(), keybase1.Seqno(1))
   324  		require.Equal(t, state.GetLatestHighLinkID(), keybase1.LinkID("6828ac32019bd8c0327ea57cd21b6182c3b6e4d4080182bb0119f9a630833f51"))
   325  
   326  		checkRole := func(uid keybase1.UID, role keybase1.TeamRole) {
   327  			uv := NewUserVersion(uid, 1)
   328  			r, err := state.GetUserRole(uv)
   329  			require.NoError(t, err)
   330  			require.Equal(t, role, r)
   331  		}
   332  
   333  		checkRole(keybase1.UID("99759da4f968b16121ece44652f01a19"), keybase1.TeamRole_OWNER)  // the 'doug' user
   334  		checkRole(keybase1.UID("b720a648e02b99c10d50de0c4f265419"), keybase1.TeamRole_ADMIN)  // the 'charlie' user
   335  		checkRole(keybase1.UID("921f0e1f2632277cc1fa6600e0906819"), keybase1.TeamRole_WRITER) // the 'bob' user
   336  		checkRole(keybase1.UID("c8f463c79c83fec675c398b6aa3fa719"), keybase1.TeamRole_WRITER) // changed role for 'alice'; used to be a reader
   337  
   338  		xs, err := state.GetUsersWithRole(keybase1.TeamRole_OWNER)
   339  		require.NoError(t, err)
   340  		require.Len(t, xs, 1)
   341  		xs, err = state.GetUsersWithRole(keybase1.TeamRole_WRITER)
   342  		require.NoError(t, err)
   343  		require.Len(t, xs, 2)
   344  		xs, err = state.GetUsersWithRole(keybase1.TeamRole_READER)
   345  		require.NoError(t, err)
   346  		require.Len(t, xs, 0)
   347  		xs, err = state.GetUsersWithRole(keybase1.TeamRole_BOT)
   348  		require.NoError(t, err)
   349  		require.Len(t, xs, 0)
   350  		xs, err = state.GetUsersWithRole(keybase1.TeamRole_RESTRICTEDBOT)
   351  		require.NoError(t, err)
   352  		require.Len(t, xs, 0)
   353  
   354  		// Reserialize
   355  		bs, err := encode(state.inner)
   356  		require.NoError(t, err, "encode")
   357  		state = &TeamSigChainState{}
   358  		err = decode(bs, &state.inner)
   359  		require.NoError(t, err, "decode")
   360  	}
   361  }
   362  
   363  func encode(input interface{}) ([]byte, error) {
   364  	mh := codec.MsgpackHandle{WriteExt: true}
   365  	var data []byte
   366  	enc := codec.NewEncoderBytes(&data, &mh)
   367  	if err := enc.Encode(input); err != nil {
   368  		return nil, err
   369  	}
   370  	return data, nil
   371  }
   372  
   373  func decode(data []byte, res interface{}) error {
   374  	mh := codec.MsgpackHandle{WriteExt: true}
   375  	dec := codec.NewDecoderBytes(data, &mh)
   376  	err := dec.Decode(res)
   377  	return err
   378  }
   379  
   380  func TestTeamSigChainWithInvites(t *testing.T) {
   381  	tc := SetupTest(t, "test_team_chains", 1)
   382  	defer tc.Cleanup()
   383  
   384  	var jig DeconstructJig
   385  	err := json.Unmarshal([]byte(teamChainWithInvites), &jig)
   386  	require.NoError(t, err)
   387  
   388  	var chainLinks []SCChainLink
   389  	for i, link := range jig.Chain {
   390  
   391  		chainLink, err := ParseTeamChainLink(string(link))
   392  		require.NoError(t, err)
   393  
   394  		t.Logf("%v chainLink: %v", i, spew.Sdump(chainLink))
   395  		chainLinks = append(chainLinks, chainLink)
   396  	}
   397  
   398  	consumer := NewUserVersion("99759da4f968b16121ece44652f01a19", 1)
   399  	var state *TeamSigChainState
   400  	for _, cLink := range chainLinks {
   401  		link, err := unpackChainLink(&cLink)
   402  		require.NoError(t, err)
   403  		var signer *keybase1.UserVersion
   404  		if !link.isStubbed() {
   405  			// Assume the signing user has never reset.
   406  			signer = &keybase1.UserVersion{
   407  				Uid:         link.inner.Body.Key.UID,
   408  				EldestSeqno: keybase1.Seqno(1),
   409  			}
   410  		}
   411  		newState, err := AppendChainLink(context.TODO(), tc.G, consumer, state, link, signerToX(signer))
   412  		require.NoError(t, err)
   413  		state = &newState
   414  	}
   415  	checkInvite := func(s string, f func(i *keybase1.TeamInvite)) {
   416  		var i *keybase1.TeamInvite
   417  		tmpMD, ok := state.FindActiveInviteMDByID(keybase1.TeamInviteID(s))
   418  		if ok {
   419  			tmp := tmpMD.Invite
   420  			i = &tmp
   421  		}
   422  		f(i)
   423  	}
   424  	checkInvite("117b4f1d1048042cb67e204c84d07927", func(i *keybase1.TeamInvite) {
   425  		require.Nil(t, i)
   426  	})
   427  	checkInvite("b90e024124ddd80870759bae42143227", func(i *keybase1.TeamInvite) {
   428  		require.NotNil(t, i)
   429  		require.Equal(t, i.Role, keybase1.TeamRole_WRITER)
   430  		require.Equal(t, i.Type.Sbs(), keybase1.TeamInviteSocialNetwork("rooter"))
   431  		require.Equal(t, i.Name, keybase1.TeamInviteName("u_8114060fcef4"))
   432  	})
   433  	checkInvite("4f66ee0fa60ecb10b9f59ff6b7157527", func(i *keybase1.TeamInvite) {
   434  		require.NotNil(t, i)
   435  		require.Equal(t, i.Role, keybase1.TeamRole_WRITER)
   436  		typ, err := i.Type.C()
   437  		require.NoError(t, err)
   438  		require.Equal(t, typ, keybase1.TeamInviteCategory_EMAIL)
   439  		require.Equal(t, i.Name, keybase1.TeamInviteName("max+8114060fcef4@keyba.se"))
   440  	})
   441  	checkInvite("6acd369ec6f5649c4ef83c8753b3aa27", func(i *keybase1.TeamInvite) {
   442  		require.NotNil(t, i)
   443  		require.Equal(t, i.Role, keybase1.TeamRole_ADMIN)
   444  		require.Equal(t, i.Type.Sbs(), keybase1.TeamInviteSocialNetwork("twitter"))
   445  		require.Equal(t, i.Name, keybase1.TeamInviteName("u_8114060fcef4"))
   446  	})
   447  }
   448  
   449  func signerToX(uv *keybase1.UserVersion) *SignerX {
   450  	if uv == nil {
   451  		return nil
   452  	}
   453  	return &SignerX{signer: *uv}
   454  }
   455  
   456  func TestMemberCtime(t *testing.T) {
   457  	uv := NewUserVersion("foo", 1)
   458  	var points []keybase1.UserLogPoint
   459  	newTeamChainState := func() TeamSigChainState {
   460  		return TeamSigChainState{
   461  			inner: keybase1.TeamSigChainState{
   462  				UserLog: map[keybase1.UserVersion][]keybase1.UserLogPoint{
   463  					uv: points,
   464  				},
   465  			},
   466  		}
   467  	}
   468  
   469  	// nil points
   470  	tcs := newTeamChainState()
   471  	ctime := tcs.MemberCtime(uv)
   472  	require.Nil(t, ctime)
   473  
   474  	// user joined as a writer
   475  	points = append(points,
   476  		keybase1.UserLogPoint{
   477  			Role: keybase1.TeamRole_WRITER,
   478  			SigMeta: keybase1.SignatureMetadata{
   479  				Time: 1,
   480  			},
   481  		})
   482  	tcs = newTeamChainState()
   483  	ctime = tcs.MemberCtime(uv)
   484  	require.NotNil(t, ctime)
   485  	require.EqualValues(t, 1, *ctime)
   486  
   487  	points = append(points,
   488  		keybase1.UserLogPoint{
   489  			Role: keybase1.TeamRole_NONE,
   490  			SigMeta: keybase1.SignatureMetadata{
   491  				Time: 2,
   492  			},
   493  		},
   494  		keybase1.UserLogPoint{
   495  			Role: keybase1.TeamRole_ADMIN,
   496  			SigMeta: keybase1.SignatureMetadata{
   497  				Time: 3,
   498  			},
   499  		})
   500  
   501  	// user left and joined later as an admin
   502  	tcs = newTeamChainState()
   503  	ctime = tcs.MemberCtime(uv)
   504  	require.NotNil(t, ctime)
   505  	require.EqualValues(t, 3, *ctime)
   506  
   507  	// user had a bunch of non-NONE roles, we should return the first join time
   508  	points = nil
   509  	for i := 0; i < 5; i++ {
   510  		points = append(points,
   511  			keybase1.UserLogPoint{
   512  				Role: keybase1.TeamRole_WRITER,
   513  				SigMeta: keybase1.SignatureMetadata{
   514  					Time: keybase1.Time(i),
   515  				},
   516  			})
   517  		tcs = newTeamChainState()
   518  		ctime = tcs.MemberCtime(uv)
   519  		require.NotNil(t, ctime)
   520  		require.EqualValues(t, 0, *ctime)
   521  	}
   522  }