
@sanderpick
25Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.
hive.blog/@sanderpickVOTING POWER100.00%
DOWNVOTE POWER100.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS0.00%
Net Worth
11.817USD
HIVE
0.000HIVE
HBD
0.000HBD
Own HP
29.478HP
Detailed Balance
| HIVE | ||
| balance | 0.000HIVE | HIVE |
| market_balance | 0.000HIVE | HIVE |
| savings_balance | 0.000HIVE | HIVE |
| reward_hive_balance | 0.000HIVE | HIVE |
| HIVE POWER | ||
| Own HP | 29.478HP | HP |
| Delegated Out | 0.000HP | HP |
| Delegation In | 0.000HP | HP |
| Effective Power | 29.478HP | HP |
| Reward HP (pending) | 0.034HP | HP |
| HBD | ||
| hbd_balance | 0.000HBD | HBD |
| hbd_conversions | 0.000HBD | HBD |
| hbd_market_balance | 0.000HBD | HBD |
| savings_hbd_balance | 0.000HBD | HBD |
| reward_hbd_balance | 0.026HBD | HBD |
{
"balance": "0.000 HIVE",
"savings_balance": "0.000 HIVE",
"reward_hive_balance": "0.000 HIVE",
"vesting_shares": "47853.206961 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "0.000000 VESTS",
"hbd_balance": "0.000 HBD",
"savings_hbd_balance": "0.000 HBD",
"reward_hbd_balance": "0.026 HBD"
}Account Info
| name | sanderpick |
| id | 791306 |
| rank | 0 |
| reputation | 0 |
| created | 2018-03-02T17:19:30 |
| recovery_account | steem |
| proxy | None |
| invited_by | null |
| post_count | 3 |
| comment_count | 0 |
| lifetime_vote_count | 0 |
| witnesses_voted_for | 0 |
| last_post | 2018-11-01T21:52:03 |
| last_root_post | 2018-11-01T21:33:18 |
| last_vote_time | 2018-11-01T23:32:57 |
| proxied_vsf_votes | 0, 0, 0, 0 |
| can_vote | 1 |
| voting_power | 9,522 |
| delayed_votes | None |
| governance_vote_expiration_ts | 1969-12-31T23:59:59 |
| balance | 0.000 HIVE |
| savings_balance | 0.000 HIVE |
| hbd_balance | 0.000 HBD |
| savings_hbd_balance | 0.000 HBD |
| vesting_shares | 47853.206961 VESTS |
| delegated_vesting_shares | 0.000000 VESTS |
| received_vesting_shares | 0.000000 VESTS |
| reward_vesting_balance | 68.543020 VESTS |
| vesting_balance | 0.000 HIVE |
| vesting_withdraw_rate | 0.000000 VESTS |
| next_vesting_withdrawal | 1969-12-31T23:59:59 |
| withdrawn | 0 |
| to_withdraw | 0 |
| withdraw_routes | 0 |
| savings_withdraw_requests | 0 |
| last_account_recovery | 1970-01-01T00:00:00 |
| reset_account | null |
| last_owner_update | 1970-01-01T00:00:00 |
| last_account_update | 2018-11-01T15:23:54 |
| mined | No |
| hbd_seconds | 0 |
| hbd_last_interest_payment | 1970-01-01T00:00:00 |
| savings_hbd_last_interest_payment | 1970-01-01T00:00:00 |
{
"id": 791306,
"name": "sanderpick",
"owner": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM5miWoPgXh9Coxgg462uDpuAuBuVa5jmGEEqCxFLCzxuWovxvhy",
1
]
]
},
"active": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM87JJzyvJ9e8Rf6vm72fDaQtET6dKWPqHycSmfbvKziP4QpGHQw",
1
]
]
},
"posting": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM6BTaccrcMfAkSe4csndTguQMXXD6EXtRXE6wSeFW5p67EcZi3k",
1
]
]
},
"memo_key": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3",
"json_metadata": "{\"profile\":{\"profile_image\":\"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg\",\"cover_image\":\"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5\",\"name\":\"Sander Pick\",\"about\":\"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.\",\"location\":\"Sunnyvale, CA\",\"website\":\"https://www.textile.io\"}}",
"posting_json_metadata": "{\"profile\":{\"profile_image\":\"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg\",\"cover_image\":\"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5\",\"name\":\"Sander Pick\",\"about\":\"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.\",\"location\":\"Sunnyvale, CA\",\"website\":\"https://www.textile.io\"}}",
"proxy": "",
"previous_owner_update": "1970-01-01T00:00:00",
"last_owner_update": "1970-01-01T00:00:00",
"last_account_update": "2018-11-01T15:23:54",
"created": "2018-03-02T17:19:30",
"mined": false,
"recovery_account": "steem",
"last_account_recovery": "1970-01-01T00:00:00",
"reset_account": "null",
"comment_count": 0,
"lifetime_vote_count": 0,
"post_count": 3,
"can_vote": true,
"voting_manabar": {
"current_mana": 45566893283,
"last_update_time": 1541115177
},
"downvote_manabar": {
"current_mana": 0,
"last_update_time": 1520011167
},
"voting_power": 9522,
"balance": "0.000 HIVE",
"savings_balance": "0.000 HIVE",
"hbd_balance": "0.000 HBD",
"hbd_seconds": "0",
"hbd_seconds_last_update": "1970-01-01T00:00:00",
"hbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_hbd_balance": "0.000 HBD",
"savings_hbd_seconds": "0",
"savings_hbd_seconds_last_update": "1970-01-01T00:00:00",
"savings_hbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_withdraw_requests": 0,
"reward_hbd_balance": "0.026 HBD",
"reward_hive_balance": "0.000 HIVE",
"reward_vesting_balance": "68.543020 VESTS",
"reward_vesting_hive": "0.034 HIVE",
"vesting_shares": "47853.206961 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "0.000000 VESTS",
"vesting_withdraw_rate": "0.000000 VESTS",
"post_voting_power": "47853.206961 VESTS",
"next_vesting_withdrawal": "1969-12-31T23:59:59",
"withdrawn": 0,
"to_withdraw": 0,
"withdraw_routes": 0,
"pending_transfers": 0,
"curation_rewards": 0,
"posting_rewards": 67,
"proxied_vsf_votes": [
0,
0,
0,
0
],
"witnesses_voted_for": 0,
"last_post": "2018-11-01T21:52:03",
"last_root_post": "2018-11-01T21:33:18",
"last_vote_time": "2018-11-01T23:32:57",
"post_bandwidth": 0,
"pending_claimed_accounts": 0,
"governance_vote_expiration_ts": "1969-12-31T23:59:59",
"delayed_votes": [],
"open_recurrent_transfers": 0,
"vesting_balance": "0.000 HIVE",
"reputation": 0,
"transfer_history": [],
"market_history": [],
"post_history": [],
"vote_history": [],
"other_history": [],
"witness_votes": [],
"tags_usage": [],
"guest_bloggers": [],
"rank": 0
}Withdraw Routes
| Incoming | Outgoing |
|---|---|
Empty | Empty |
{
"incoming": [],
"outgoing": []
}From Date
To Date
2020/03/05 11:54:24
2020/03/05 11:54:24
| body | Congratulations @sanderpick! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@sanderpick/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table> <sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@sanderpick) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=sanderpick)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge"><img src="https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmTugCUsoXX762vg1CuHRrpnPbfnjPogp8iCGv7F2kSVuj/image.png"></a></td><td><a href="https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge">Use your witness votes and get the Community Badge</a></td></tr></table> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes! |
| title | |
| author | steemitboard |
| permlink | steemitboard-notify-sanderpick-20200305t115421000z |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #41385112/Trx 5e351501d174372252672e0ca5e24bbc265d66fa |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Congratulations @sanderpick! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@sanderpick/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table>\n\n<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@sanderpick) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=sanderpick)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge\"><img src=\"https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmTugCUsoXX762vg1CuHRrpnPbfnjPogp8iCGv7F2kSVuj/image.png\"></a></td><td><a href=\"https://steemit.com/steemitboard/@steemitboard/use-your-witness-votes-and-get-the-community-badge\">Use your witness votes and get the Community Badge</a></td></tr></table>\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!",
"title": "",
"author": "steemitboard",
"permlink": "steemitboard-notify-sanderpick-20200305t115421000z",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 41385112,
"trx_id": "5e351501d174372252672e0ca5e24bbc265d66fa",
"op_in_trx": 0,
"timestamp": "2020-03-05T11:54:24",
"virtual_op": false,
"trx_in_block": 4
}2019/03/02 20:06:15
2019/03/02 20:06:15
| body | Congratulations @sanderpick! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@sanderpick/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table> <sub>_[Click here to view your Board](https://steemitboard.com/@sanderpick)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/carnival/@steemitboard/carnival-2019"><img src="https://steemitimages.com/64x128/http://i.cubeupload.com/rltzHT.png"></a></td><td><a href="https://steemit.com/carnival/@steemitboard/carnival-2019">Carnival Challenge - Collect badge and win 5 STEEM</a></td></tr></table> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) and get one more award and increased upvotes! |
| title | |
| author | steemitboard |
| permlink | steemitboard-notify-sanderpick-20190302t200611000z |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #30809751/Trx 5349ab4064f71f1564f9ce5db3d3778182b91f1f |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Congratulations @sanderpick! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@sanderpick/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table>\n\n<sub>_[Click here to view your Board](https://steemitboard.com/@sanderpick)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/carnival/@steemitboard/carnival-2019\"><img src=\"https://steemitimages.com/64x128/http://i.cubeupload.com/rltzHT.png\"></a></td><td><a href=\"https://steemit.com/carnival/@steemitboard/carnival-2019\">Carnival Challenge - Collect badge and win 5 STEEM</a></td></tr></table>\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) and get one more award and increased upvotes!",
"title": "",
"author": "steemitboard",
"permlink": "steemitboard-notify-sanderpick-20190302t200611000z",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 30809751,
"trx_id": "5349ab4064f71f1564f9ce5db3d3778182b91f1f",
"op_in_trx": 0,
"timestamp": "2019-03-02T20:06:15",
"virtual_op": false,
"trx_in_block": 3
}steemdelegated 0.000 HP to @sanderpick2018/11/26 19:21:15
steemdelegated 0.000 HP to @sanderpick
2018/11/26 19:21:15
| delegatee | sanderpick |
| delegator | steem |
| vesting shares | 0.000000 VESTS |
| Transaction Info | Block #28046165/Trx 57d3bdcb6f92bfbd0f2d5e5e178126793abbf156 |
View Raw JSON Data
{
"op": [
"delegate_vesting_shares",
{
"delegatee": "sanderpick",
"delegator": "steem",
"vesting_shares": "0.000000 VESTS"
}
],
"block": 28046165,
"trx_id": "57d3bdcb6f92bfbd0f2d5e5e178126793abbf156",
"op_in_trx": 1,
"timestamp": "2018-11-26T19:21:15",
"virtual_op": false,
"trx_in_block": 11
}2018/11/08 21:52:03
2018/11/08 21:52:03
| author | sanderpick |
| permlink | re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t215203511z |
| Transaction Info | Block #27531080/Virtual Operation 4294967295:6 |
View Raw JSON Data
{
"op": [
"comment_payout_update",
{
"author": "sanderpick",
"permlink": "re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t215203511z"
}
],
"block": 27531080,
"trx_id": "0000000000000000000000000000000000000000",
"op_in_trx": 6,
"timestamp": "2018-11-08T21:52:03",
"virtual_op": true,
"trx_in_block": 4294967295
}2018/11/08 21:33:18
2018/11/08 21:33:18
| author | sanderpick |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27530705/Virtual Operation 4294967295:6 |
View Raw JSON Data
{
"op": [
"comment_payout_update",
{
"author": "sanderpick",
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27530705,
"trx_id": "0000000000000000000000000000000000000000",
"op_in_trx": 6,
"timestamp": "2018-11-08T21:33:18",
"virtual_op": true,
"trx_in_block": 4294967295
}sanderpickreceived 0.059 HBD reward share for hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/08 21:33:18
sanderpickreceived 0.059 HBD reward share for hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/08 21:33:18
| author | sanderpick |
| payout | 0.059 HBD |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| author rewards | 67 |
| total payout value | 0.052 HBD |
| curator payout value | 0.006 HBD |
| beneficiary payout value | 0.000 HBD |
| Transaction Info | Block #27530705/Virtual Operation 4294967295:5 |
View Raw JSON Data
{
"op": [
"comment_reward",
{
"author": "sanderpick",
"payout": "0.059 HBD",
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"author_rewards": 67,
"total_payout_value": "0.052 HBD",
"curator_payout_value": "0.006 HBD",
"beneficiary_payout_value": "0.000 HBD"
}
],
"block": 27530705,
"trx_id": "0000000000000000000000000000000000000000",
"op_in_trx": 5,
"timestamp": "2018-11-08T21:33:18",
"virtual_op": true,
"trx_in_block": 4294967295
}sanderpickreceived 0.026 HBD, 0.042 HP author reward for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/08 21:33:18
sanderpickreceived 0.026 HBD, 0.042 HP author reward for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/08 21:33:18
| author | sanderpick |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| hbd payout | 0.026 HBD |
| hive payout | 0.000 HIVE |
| vesting payout | 68.543020 VESTS |
| payout must be claimed | true |
| curators vesting payout | 16.127769 VESTS |
| Transaction Info | Block #27530705/Virtual Operation 4294967295:4 |
View Raw JSON Data
{
"op": [
"author_reward",
{
"author": "sanderpick",
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"hbd_payout": "0.026 HBD",
"hive_payout": "0.000 HIVE",
"vesting_payout": "68.543020 VESTS",
"payout_must_be_claimed": true,
"curators_vesting_payout": "16.127769 VESTS"
}
],
"block": 27530705,
"trx_id": "0000000000000000000000000000000000000000",
"op_in_trx": 4,
"timestamp": "2018-11-08T21:33:18",
"virtual_op": true,
"trx_in_block": 4294967295
}2018/11/08 16:30:27
2018/11/08 16:30:27
| author | sanderpick |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27524649/Virtual Operation 4294967295:4 |
View Raw JSON Data
{
"op": [
"comment_payout_update",
{
"author": "sanderpick",
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27524649,
"trx_id": "0000000000000000000000000000000000000000",
"op_in_trx": 4,
"timestamp": "2018-11-08T16:30:27",
"virtual_op": true,
"trx_in_block": 4294967295
}johnnyyyeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/05 10:58:45
johnnyyyeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/05 10:58:45
| voter | johnnyyy |
| author | sanderpick |
| weight | 1064 (10.64%) |
| rshares | 558180116 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.072 HBD |
| total vote weight | 249140 |
| Transaction Info | Block #27431695/Trx e31b13a506828d497ee2ee0d90dbf4fbe9340962 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "johnnyyy",
"author": "sanderpick",
"weight": 1064,
"rshares": 558180116,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.072 HBD",
"total_vote_weight": 249140
}
],
"block": 27431695,
"trx_id": "e31b13a506828d497ee2ee0d90dbf4fbe9340962",
"op_in_trx": 1,
"timestamp": "2018-11-05T10:58:45",
"virtual_op": true,
"trx_in_block": 3
}2018/11/05 10:58:45
2018/11/05 10:58:45
| voter | johnnyyy |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27431695/Trx e31b13a506828d497ee2ee0d90dbf4fbe9340962 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "johnnyyy",
"author": "sanderpick",
"weight": 10000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27431695,
"trx_id": "e31b13a506828d497ee2ee0d90dbf4fbe9340962",
"op_in_trx": 0,
"timestamp": "2018-11-05T10:58:45",
"virtual_op": false,
"trx_in_block": 3
}2018/11/02 02:56:06
2018/11/02 02:56:06
| body | Hi, Welcome to the amazing world of Blogging and Sharing on Steem Blockchain. Make sure to post only Original content and avoid all kind of plagarism. Keep your patience and faith to get success here. Also make sure to engage and contribite to Community development. Commenting is best part to get involve. Hope you have a happy stay.....Steem On! Thanks [@steemflow](https://steemit.com/@steemflow) Posted using [Partiko Android](https://steemit.com/@partiko-android) |
| title | |
| author | steemflow |
| permlink | steemflow-re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181102t025603128z |
| json metadata | {"app":"partiko"} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27335729/Trx b6df9faaf2bfdff5ef8dd8c6957845937d343d76 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Hi, Welcome to the amazing world of Blogging and Sharing on Steem Blockchain. Make sure to post only Original content and avoid all kind of plagarism.\nKeep your patience and faith to get success here. Also make sure to engage and contribite to Community development. Commenting is best part to get involve. Hope you have a happy stay.....Steem On!\nThanks\n[@steemflow](https://steemit.com/@steemflow)\n\nPosted using [Partiko Android](https://steemit.com/@partiko-android)",
"title": "",
"author": "steemflow",
"permlink": "steemflow-re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181102t025603128z",
"json_metadata": "{\"app\":\"partiko\"}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27335729,
"trx_id": "b6df9faaf2bfdff5ef8dd8c6957845937d343d76",
"op_in_trx": 0,
"timestamp": "2018-11-02T02:56:06",
"virtual_op": false,
"trx_in_block": 22
}jalayneffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/02 01:17:21
jalayneffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/02 01:17:21
| voter | jalayn |
| author | sanderpick |
| weight | 29865 |
| rshares | 15657544265 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.071 HBD |
| total vote weight | 248076 |
| Transaction Info | Block #27333754/Trx a27dcbb4182548a106f20759e539abe9c2df9070 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "jalayn",
"author": "sanderpick",
"weight": 29865,
"rshares": 15657544265,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.071 HBD",
"total_vote_weight": 248076
}
],
"block": 27333754,
"trx_id": "a27dcbb4182548a106f20759e539abe9c2df9070",
"op_in_trx": 1,
"timestamp": "2018-11-02T01:17:21",
"virtual_op": true,
"trx_in_block": 11
}2018/11/02 01:17:21
2018/11/02 01:17:21
| voter | jalayn |
| author | sanderpick |
| weight | 7000 (70.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27333754/Trx a27dcbb4182548a106f20759e539abe9c2df9070 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "jalayn",
"author": "sanderpick",
"weight": 7000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27333754,
"trx_id": "a27dcbb4182548a106f20759e539abe9c2df9070",
"op_in_trx": 0,
"timestamp": "2018-11-02T01:17:21",
"virtual_op": false,
"trx_in_block": 11
}2018/11/02 00:50:57
2018/11/02 00:50:57
| body | Congratulations @sanderpick! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) : <table><tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstpost.png</td><td>You published your First Post</td></tr> <tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstvoted.png</td><td>You got a First Vote</td></tr> <tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstcomment.png</td><td>You made your First Comment</td></tr> <tr><td>https://steemitimages.com/60x70/http://steemitboard.com/@sanderpick/voted.png?201811012312</td><td>You received more than 10 upvotes. Your next target is to reach 50 upvotes.</td></tr> <tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstvote.png</td><td>You made your First Vote</td></tr> </table> <sub>_[Click here to view your Board of Honor](https://steemitboard.com/@sanderpick)_</sub> <sub>_If you no longer want to receive notifications, reply to this comment with the word_ `STOP`</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/steemfest/@steemitboard/i06trehc"><img src="https://steemitimages.com/64x128/https://ipfs.io/ipfs/QmU34ZrY632FFKQ1vbrkSM27VcnsjQdtXPynfMrpxDFJcF"></a></td><td><a href="https://steemit.com/steemfest/@steemitboard/i06trehc">Be ready for the next contest!</a></td></tr><tr><td><a href="https://steemit.com/halloween/@steemitboard/trick-or-treat-publish-your-scariest-halloweeen-story-and-win-a-new-badge"><img src="https://steemitimages.com/64x128/http://i.cubeupload.com/RUyB3u.png"></a></td><td><a href="https://steemit.com/halloween/@steemitboard/trick-or-treat-publish-your-scariest-halloweeen-story-and-win-a-new-badge">Trick or Treat - Publish your scariest halloween story and win a new badge</a></td></tr></table> > Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**! |
| title | |
| author | steemitboard |
| permlink | steemitboard-notify-sanderpick-20181102t005053000z |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27333226/Trx 143d4e15f6bb7ac9c5b94d32dc84dfa967a0f7c4 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Congratulations @sanderpick! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :\n\n<table><tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstpost.png</td><td>You published your First Post</td></tr>\n<tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstvoted.png</td><td>You got a First Vote</td></tr>\n<tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstcomment.png</td><td>You made your First Comment</td></tr>\n<tr><td>https://steemitimages.com/60x70/http://steemitboard.com/@sanderpick/voted.png?201811012312</td><td>You received more than 10 upvotes. Your next target is to reach 50 upvotes.</td></tr>\n<tr><td>https://steemitimages.com/60x60/http://steemitboard.com/notifications/firstvote.png</td><td>You made your First Vote</td></tr>\n</table>\n\n<sub>_[Click here to view your Board of Honor](https://steemitboard.com/@sanderpick)_</sub>\n<sub>_If you no longer want to receive notifications, reply to this comment with the word_ `STOP`</sub>\n\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/steemfest/@steemitboard/i06trehc\"><img src=\"https://steemitimages.com/64x128/https://ipfs.io/ipfs/QmU34ZrY632FFKQ1vbrkSM27VcnsjQdtXPynfMrpxDFJcF\"></a></td><td><a href=\"https://steemit.com/steemfest/@steemitboard/i06trehc\">Be ready for the next contest!</a></td></tr><tr><td><a href=\"https://steemit.com/halloween/@steemitboard/trick-or-treat-publish-your-scariest-halloweeen-story-and-win-a-new-badge\"><img src=\"https://steemitimages.com/64x128/http://i.cubeupload.com/RUyB3u.png\"></a></td><td><a href=\"https://steemit.com/halloween/@steemitboard/trick-or-treat-publish-your-scariest-halloweeen-story-and-win-a-new-badge\">Trick or Treat - Publish your scariest halloween story and win a new badge</a></td></tr></table>\n\n> Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**!",
"title": "",
"author": "steemitboard",
"permlink": "steemitboard-notify-sanderpick-20181102t005053000z",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27333226,
"trx_id": "143d4e15f6bb7ac9c5b94d32dc84dfa967a0f7c4",
"op_in_trx": 0,
"timestamp": "2018-11-02T00:50:57",
"virtual_op": false,
"trx_in_block": 0
}sanderpickeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 23:33:00
sanderpickeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 23:33:00
| voter | sanderpick |
| author | sanderpick |
| weight | 2066 (20.66%) |
| rshares | 1083231985 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.053 HBD |
| total vote weight | 218211 |
| Transaction Info | Block #27331668/Trx 16179551cedbe166c080026217920f24917d73f7 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "sanderpick",
"author": "sanderpick",
"weight": 2066,
"rshares": 1083231985,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.053 HBD",
"total_vote_weight": 218211
}
],
"block": 27331668,
"trx_id": "16179551cedbe166c080026217920f24917d73f7",
"op_in_trx": 1,
"timestamp": "2018-11-01T23:33:00",
"virtual_op": true,
"trx_in_block": 13
}2018/11/01 23:33:00
2018/11/01 23:33:00
| voter | sanderpick |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27331668/Trx 16179551cedbe166c080026217920f24917d73f7 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "sanderpick",
"author": "sanderpick",
"weight": 10000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27331668,
"trx_id": "16179551cedbe166c080026217920f24917d73f7",
"op_in_trx": 0,
"timestamp": "2018-11-01T23:33:00",
"virtual_op": false,
"trx_in_block": 13
}sanderpickeffective vote applied for @andrewxhill / introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs2018/11/01 23:32:36
sanderpickeffective vote applied for @andrewxhill / introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs
2018/11/01 23:32:36
| voter | sanderpick |
| author | andrewxhill |
| weight | 22782 |
| rshares | 1106293619 |
| permlink | introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs |
| pending payout | 0.001 HBD |
| total vote weight | 35555 |
| Transaction Info | Block #27331660/Trx 9c9cdd424b774741bb4093e496331f6ede04630b |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "sanderpick",
"author": "andrewxhill",
"weight": 22782,
"rshares": 1106293619,
"permlink": "introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs",
"pending_payout": "0.001 HBD",
"total_vote_weight": 35555
}
],
"block": 27331660,
"trx_id": "9c9cdd424b774741bb4093e496331f6ede04630b",
"op_in_trx": 1,
"timestamp": "2018-11-01T23:32:36",
"virtual_op": true,
"trx_in_block": 9
}2018/11/01 23:32:36
2018/11/01 23:32:36
| voter | sanderpick |
| author | andrewxhill |
| weight | 10000 (100.00%) |
| permlink | introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs |
| Transaction Info | Block #27331660/Trx 9c9cdd424b774741bb4093e496331f6ede04630b |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "sanderpick",
"author": "andrewxhill",
"weight": 10000,
"permlink": "introducing-textile-a-decentralized-end-to-end-encrypted-photo-sharing-app-built-on-ipfs"
}
],
"block": 27331660,
"trx_id": "9c9cdd424b774741bb4093e496331f6ede04630b",
"op_in_trx": 0,
"timestamp": "2018-11-01T23:32:36",
"virtual_op": false,
"trx_in_block": 9
}sanderpickfollowed @andrewxhill2018/11/01 23:31:57
sanderpickfollowed @andrewxhill
2018/11/01 23:31:57
| id | follow |
| json | ["follow",{"follower":"sanderpick","following":"andrewxhill","what":["blog"]}] |
| required auths | [] |
| required posting auths | ["sanderpick"] |
| Transaction Info | Block #27331647/Trx 86c9736eefc91aad041befe5dbb9baa96a0b3738 |
View Raw JSON Data
{
"op": [
"custom_json",
{
"id": "follow",
"json": "[\"follow\",{\"follower\":\"sanderpick\",\"following\":\"andrewxhill\",\"what\":[\"blog\"]}]",
"required_auths": [],
"required_posting_auths": [
"sanderpick"
]
}
],
"block": 27331647,
"trx_id": "86c9736eefc91aad041befe5dbb9baa96a0b3738",
"op_in_trx": 0,
"timestamp": "2018-11-01T23:31:57",
"virtual_op": false,
"trx_in_block": 14
}newforyoueffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 22:32:03
newforyoueffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 22:32:03
| voter | newforyou |
| author | sanderpick |
| weight | 76790 |
| rshares | 25251878202 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.051 HBD |
| total vote weight | 216145 |
| Transaction Info | Block #27330449/Trx c213e988147d1a782a982c0d1d7d415e0005f5a0 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "newforyou",
"author": "sanderpick",
"weight": 76790,
"rshares": 25251878202,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.051 HBD",
"total_vote_weight": 216145
}
],
"block": 27330449,
"trx_id": "c213e988147d1a782a982c0d1d7d415e0005f5a0",
"op_in_trx": 1,
"timestamp": "2018-11-01T22:32:03",
"virtual_op": true,
"trx_in_block": 21
}2018/11/01 22:32:03
2018/11/01 22:32:03
| voter | newforyou |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27330449/Trx c213e988147d1a782a982c0d1d7d415e0005f5a0 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "newforyou",
"author": "sanderpick",
"weight": 10000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27330449,
"trx_id": "c213e988147d1a782a982c0d1d7d415e0005f5a0",
"op_in_trx": 0,
"timestamp": "2018-11-01T22:32:03",
"virtual_op": false,
"trx_in_block": 21
}josexaviereffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 21:54:12
josexaviereffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 21:54:12
| voter | josexavier |
| author | sanderpick |
| weight | 2029 (20.29%) |
| rshares | 531797410 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.022 HBD |
| total vote weight | 139355 |
| Transaction Info | Block #27329692/Trx 5850c6cd7ac78a999ed6bafc7a1664354bdb5532 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "josexavier",
"author": "sanderpick",
"weight": 2029,
"rshares": 531797410,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.022 HBD",
"total_vote_weight": 139355
}
],
"block": 27329692,
"trx_id": "5850c6cd7ac78a999ed6bafc7a1664354bdb5532",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:54:12",
"virtual_op": true,
"trx_in_block": 23
}2018/11/01 21:54:12
2018/11/01 21:54:12
| voter | josexavier |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329692/Trx 5850c6cd7ac78a999ed6bafc7a1664354bdb5532 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "josexavier",
"author": "sanderpick",
"weight": 10000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329692,
"trx_id": "5850c6cd7ac78a999ed6bafc7a1664354bdb5532",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:54:12",
"virtual_op": false,
"trx_in_block": 23
}2018/11/01 21:52:06
2018/11/01 21:52:06
| body | Here's an intro to Textile from @andrewxhill: We've been in private beta through the Fall and are getting very close to a public release. We wanted to invite Steem'ers to give it an early try though. Please keep in mind that it's Beta, the app is still changing a lot between each release. We also have a major migration planned in a week in order to get users a bunch of cool new features.  Brief overview: - Textile runs an IPFS node directly on your mobile device - Photos you add to the app will be privately encrypted with a key only you have access to, and hashed for IPFS. - You can create Threads, which are small groups to share photos with, you all share a set of keys specific to the Thread. - When you post a photo to a Thread, you share the photo key to Thread members so they can fetch it and view it. Here's another technical post on our encryption: [The 5 steps to end-to-end encrypted photo storage and sharing](https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14) If you are still interested, you can join me with the referral code, 'OCCER' Just enter it after you download the app for iOS or Android. Then, after you have the app up and running, click the following link from your phone to join my Steem Thread to test out sharing. https://www.textile.photos/invites/new#id=QmNxaZpX2EvsJ9GGvxzqHBdRnzF5GkGNFeX3fRX2n7N4FJ&key=1COZFJ77NuJhCeOuoIdlf8n8ajU1COZFCR0fSKN3fGcr&inviter=andrewxhill&name=Steem&referral=MSCES |
| title | |
| author | sanderpick |
| permlink | re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t215203511z |
| json metadata | {"tags":["introduceyourself"],"users":["andrewxhill"],"image":["https://cdn.steemitimages.com/DQmZveBGwjTs1LviXkneXsBPXTwe12JHDRfchAe9XWfNigH/IMG_0844.png"],"links":["https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14","https://www.textile.photos/invites/new#id=QmNxaZpX2EvsJ9GGvxzqHBdRnzF5GkGNFeX3fRX2n7N4FJ&key=1COZFJ77NuJhCeOuoIdlf8n8ajU1COZFCR0fSKN3fGcr&inviter=andrewxhill&name=Steem&referral=MSCES"],"app":"steemit/0.1"} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329650/Trx 4ccb25e7c408f9c8d9609c5a2c6c8d391a8c2e38 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Here's an intro to Textile from @andrewxhill:\n\nWe've been in private beta through the Fall and are getting very close to a public release. We wanted to invite Steem'ers to give it an early try though. Please keep in mind that it's Beta, the app is still changing a lot between each release. We also have a major migration planned in a week in order to get users a bunch of cool new features.\n\n\n\nBrief overview:\n- Textile runs an IPFS node directly on your mobile device\n- Photos you add to the app will be privately encrypted with a key only you have access to, and hashed for IPFS.\n- You can create Threads, which are small groups to share photos with, you all share a set of keys specific to the Thread.\n- When you post a photo to a Thread, you share the photo key to Thread members so they can fetch it and view it.\n\nHere's another technical post on our encryption: [The 5 steps to end-to-end encrypted photo storage and sharing](https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14)\n\nIf you are still interested, you can join me with the referral code, 'OCCER'\n\nJust enter it after you download the app for iOS or Android.\n\nThen, after you have the app up and running, click the following link from your phone to join my Steem Thread to test out sharing.\n\nhttps://www.textile.photos/invites/new#id=QmNxaZpX2EvsJ9GGvxzqHBdRnzF5GkGNFeX3fRX2n7N4FJ&key=1COZFJ77NuJhCeOuoIdlf8n8ajU1COZFCR0fSKN3fGcr&inviter=andrewxhill&name=Steem&referral=MSCES",
"title": "",
"author": "sanderpick",
"permlink": "re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t215203511z",
"json_metadata": "{\"tags\":[\"introduceyourself\"],\"users\":[\"andrewxhill\"],\"image\":[\"https://cdn.steemitimages.com/DQmZveBGwjTs1LviXkneXsBPXTwe12JHDRfchAe9XWfNigH/IMG_0844.png\"],\"links\":[\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\",\"https://www.textile.photos/invites/new#id=QmNxaZpX2EvsJ9GGvxzqHBdRnzF5GkGNFeX3fRX2n7N4FJ&key=1COZFJ77NuJhCeOuoIdlf8n8ajU1COZFCR0fSKN3fGcr&inviter=andrewxhill&name=Steem&referral=MSCES\"],\"app\":\"steemit/0.1\"}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329650,
"trx_id": "4ccb25e7c408f9c8d9609c5a2c6c8d391a8c2e38",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:52:06",
"virtual_op": false,
"trx_in_block": 10
}andrewxhilleffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 21:41:57
andrewxhilleffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 21:41:57
| voter | andrewxhill |
| author | sanderpick |
| weight | 333 (3.33%) |
| rshares | 152762950 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.021 HBD |
| total vote weight | 137326 |
| Transaction Info | Block #27329447/Trx da53d9ce4f26483940b49df744d038a0e49c3b4e |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "andrewxhill",
"author": "sanderpick",
"weight": 333,
"rshares": 152762950,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.021 HBD",
"total_vote_weight": 137326
}
],
"block": 27329447,
"trx_id": "da53d9ce4f26483940b49df744d038a0e49c3b4e",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:41:57",
"virtual_op": true,
"trx_in_block": 2
}2018/11/01 21:41:57
2018/11/01 21:41:57
| voter | andrewxhill |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329447/Trx da53d9ce4f26483940b49df744d038a0e49c3b4e |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "andrewxhill",
"author": "sanderpick",
"weight": 10000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329447,
"trx_id": "da53d9ce4f26483940b49df744d038a0e49c3b4e",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:41:57",
"virtual_op": false,
"trx_in_block": 2
}earncryptoeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 21:34:48
earncryptoeffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 21:34:48
| voter | earncrypto |
| author | sanderpick |
| weight | 6268 (62.68%) |
| rshares | 13538473732 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.021 HBD |
| total vote weight | 136744 |
| Transaction Info | Block #27329304/Trx fc8d40e7b9e2c0358daf4404ee2af19589130936 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "earncrypto",
"author": "sanderpick",
"weight": 6268,
"rshares": 13538473732,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.021 HBD",
"total_vote_weight": 136744
}
],
"block": 27329304,
"trx_id": "fc8d40e7b9e2c0358daf4404ee2af19589130936",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:34:48",
"virtual_op": true,
"trx_in_block": 3
}2018/11/01 21:34:48
2018/11/01 21:34:48
| voter | earncrypto |
| author | sanderpick |
| weight | 4000 (40.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329304/Trx fc8d40e7b9e2c0358daf4404ee2af19589130936 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "earncrypto",
"author": "sanderpick",
"weight": 4000,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329304,
"trx_id": "fc8d40e7b9e2c0358daf4404ee2af19589130936",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:34:48",
"virtual_op": false,
"trx_in_block": 3
}esteemappreplied to @sanderpick / re-2018111t163439830z2018/11/01 21:34:42
esteemappreplied to @sanderpick / re-2018111t163439830z
2018/11/01 21:34:42
| body | Welcome sanderpick! <br>**eSteem** is the application that improves your experience here. We have Mobile application for [Android](https://play.google.com/store/apps/details?id=com.netsolutions.esteem) and [iOS](https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1141397898&mt=8) users. We also have developed [Surfer Desktop](https://github.com/esteemapp/esteem-surfer/releases) application that helps you to gain new followers and stay connected with your friends, unique features - notifications, bookmarks, favorites, drafts, and more. <br> We reward our users with encouragement upvotes as well as monthly giveaways rewarding Spotlight top users and active Discord users. <br>Learn more: https://esteem.app <br>Join our discord: https://discord.gg/8eHupPq |
| title | |
| author | esteemapp |
| permlink | re-2018111t163439830z |
| json metadata | {"tags":["esteem"],"app":"esteem/1.0-welcome","format":"markdown+html","community":"esteem.app"} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329302/Trx bc18b5549fb764dde011b7e125f0795d921e3964 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Welcome sanderpick! <br>**eSteem** is the application that improves your experience here. We have Mobile application for [Android](https://play.google.com/store/apps/details?id=com.netsolutions.esteem) and [iOS](https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1141397898&mt=8) users. We also have developed [Surfer Desktop](https://github.com/esteemapp/esteem-surfer/releases) application that helps you to gain new followers and stay connected with your friends, unique features - notifications, bookmarks, favorites, drafts, and more. <br> We reward our users with encouragement upvotes as well as monthly giveaways rewarding Spotlight top users and active Discord users. <br>Learn more: https://esteem.app <br>Join our discord: https://discord.gg/8eHupPq",
"title": "",
"author": "esteemapp",
"permlink": "re-2018111t163439830z",
"json_metadata": "{\"tags\":[\"esteem\"],\"app\":\"esteem/1.0-welcome\",\"format\":\"markdown+html\",\"community\":\"esteem.app\"}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329302,
"trx_id": "bc18b5549fb764dde011b7e125f0795d921e3964",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:34:42",
"virtual_op": false,
"trx_in_block": 31
}2018/11/01 21:33:39
2018/11/01 21:33:39
| body | <center> Welcome to Steem @sanderpick. <br/><br/>Do read [A thumb rule for steemit minnows - 50:100:200:25](https://steemit.com/steemit/@steemladder/a-thumb-rule-for-steemit-minnows-50-100-200-25) for starter tips. <br/> <br/>Spend time reading [Steem Blue Paper](https://steem.io/steem-bluepaper.pdf) to know how Steem blockchain works and if you still have any queries ask them on our [Ask me anything about Steemit post](https://steemit.com/steem/@steemladder/there-are-no-dumb-questions-ask-anything-you-want-to-know-about-steem-blockchain) and we will try to answer that.<br/><br/>All the Best!!! </center> |
| title | Welcome to Steem! |
| author | steemladder |
| permlink | re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t213326545z |
| json metadata | {"tags":["steemladder"]} |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329281/Trx a2693b27f74590f887214879b98ddf333338f3aa |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "<center>\nWelcome to Steem @sanderpick. <br/><br/>Do read [A thumb rule for steemit minnows - 50:100:200:25](https://steemit.com/steemit/@steemladder/a-thumb-rule-for-steemit-minnows-50-100-200-25) for starter tips. <br/> <br/>Spend time reading [Steem Blue Paper](https://steem.io/steem-bluepaper.pdf) to know how Steem blockchain works and if you still have any queries ask them on our [Ask me anything about Steemit post](https://steemit.com/steem/@steemladder/there-are-no-dumb-questions-ask-anything-you-want-to-know-about-steem-blockchain) and we will try to answer that.<br/><br/>All the Best!!!\n</center>",
"title": "Welcome to Steem!",
"author": "steemladder",
"permlink": "re-sanderpick-hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol-20181101t213326545z",
"json_metadata": "{\"tags\":[\"steemladder\"]}",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329281,
"trx_id": "a2693b27f74590f887214879b98ddf333338f3aa",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:33:39",
"virtual_op": false,
"trx_in_block": 12
}2018/11/01 21:33:39
2018/11/01 21:33:39
| body | Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in: https://medium.com/textileio/wip-textile-threads-whitepaper-just-kidding-6ce3a6624338 |
| title | |
| author | cheetah |
| permlink | cheetah-re-sanderpickhi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| json metadata | |
| parent author | sanderpick |
| parent permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329281/Trx 4ea71888a802c39c2dd946a87202ca4d75760b76 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:\nhttps://medium.com/textileio/wip-textile-threads-whitepaper-just-kidding-6ce3a6624338",
"title": "",
"author": "cheetah",
"permlink": "cheetah-re-sanderpickhi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"json_metadata": "",
"parent_author": "sanderpick",
"parent_permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329281,
"trx_id": "4ea71888a802c39c2dd946a87202ca4d75760b76",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:33:39",
"virtual_op": false,
"trx_in_block": 10
}steemladdereffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 21:33:33
steemladdereffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 21:33:33
| voter | steemladder |
| author | sanderpick |
| weight | 737 (7.37%) |
| rshares | 4854474170 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.005 HBD |
| total vote weight | 71893 |
| Transaction Info | Block #27329279/Trx 25667ed205a7a63181695710cfdbc8536d29f305 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "steemladder",
"author": "sanderpick",
"weight": 737,
"rshares": 4854474170,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.005 HBD",
"total_vote_weight": 71893
}
],
"block": 27329279,
"trx_id": "25667ed205a7a63181695710cfdbc8536d29f305",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:33:33",
"virtual_op": true,
"trx_in_block": 15
}2018/11/01 21:33:33
2018/11/01 21:33:33
| voter | steemladder |
| author | sanderpick |
| weight | 500 (5.00%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329279/Trx 25667ed205a7a63181695710cfdbc8536d29f305 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "steemladder",
"author": "sanderpick",
"weight": 500,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329279,
"trx_id": "25667ed205a7a63181695710cfdbc8536d29f305",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:33:33",
"virtual_op": false,
"trx_in_block": 15
}cheetaheffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol2018/11/01 21:33:30
cheetaheffective vote applied for @sanderpick / hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol
2018/11/01 21:33:30
| voter | cheetah |
| author | sanderpick |
| weight | 165 (1.65%) |
| rshares | 273810282 |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| pending payout | 0.000 HBD |
| total vote weight | 16548 |
| Transaction Info | Block #27329278/Trx 380c9aea11b3999de4171de5b8324872c70c8206 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "cheetah",
"author": "sanderpick",
"weight": 165,
"rshares": 273810282,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"pending_payout": "0.000 HBD",
"total_vote_weight": 16548
}
],
"block": 27329278,
"trx_id": "380c9aea11b3999de4171de5b8324872c70c8206",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:33:30",
"virtual_op": true,
"trx_in_block": 1
}2018/11/01 21:33:30
2018/11/01 21:33:30
| voter | cheetah |
| author | sanderpick |
| weight | 8 (0.08%) |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| Transaction Info | Block #27329278/Trx 380c9aea11b3999de4171de5b8324872c70c8206 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "cheetah",
"author": "sanderpick",
"weight": 8,
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol"
}
],
"block": 27329278,
"trx_id": "380c9aea11b3999de4171de5b8324872c70c8206",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:33:30",
"virtual_op": false,
"trx_in_block": 1
}2018/11/01 21:33:21
2018/11/01 21:33:21
| body | <html> <p><em>Written by </em><a href="https://medium.com/@carsonfarmer"><em>Carson Farmer</em></a><em> & </em><a href="https://medium.com/@sanderpick"><em>Sander Pick</em></a></p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png" width="800" height="565"/><em>Download the app, take a picture, share!</em></center></p> <p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p> <p><strong>Why we built it</strong><br> We designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p> <p><strong>What makes Threads different</strong><br> Threads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p> <p><strong>Why Threads are exciting</strong><br> Because photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p> <p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href="https://realm.io/">Realm</a> or <a href="https://firebase.google.com/">Firebase</a> but <em>without</em> the centralized server.</p> <p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p> <h3>The experience</h3> <p><a href="https://textile.photos/">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif" width="400" height="600"/></center></p> <h3>Requirements</h3> <p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p> <ol> <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br> To enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li> <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br> Since we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li> <li><strong>A way to avoid state conflicts with other members of the group</strong><br> On top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li> <li><strong>A mechanism to recover the full state from the network as a whole</strong><br> Another important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href="https://en.wikipedia.org/wiki/Bitcoin">Bitcoin</a>, where new nodes are able to <a href="https://bitcoin.org/en/developer-guide#initial-block-download">download the full blockchain</a> from any connected peer.</li> <li><strong>Way to link updates via their content, rather than where they are stored</strong><br> Since we are building on top of the IPFS network, and would like to eventually support a <a href="https://filecoin.io/">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li> </ol> <p>With these requirements in mind, let’s break down our solutions into their individual components…</p> <h3>Solutions</h3> <h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4> <p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href="https://en.wikipedia.org/wiki/Peer-to-peer">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href="https://en.wikipedia.org/wiki/Lists_of_network_protocols">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p> <p>At Textile, we use the super amazing <a href="https://libp2p.io/">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href="https://ipfs.io/">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png" width="800" height="316"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p> <blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote> <p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href="https://github.com/libp2p/go-libp2p-secio">secio</a> <a href="https://github.com/libp2p/go-stream-security">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p> <p>Like many IPFS-based projects, Textile uses <a href="https://developers.google.com/protocol-buffers/">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">this previous article</a>. You can also check out how we structure our <a href="https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto">Protobuf messages</a>, learn a bit more about <a href="https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel">how secio works</a>, plus check out some <a href="https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca">recent updates</a> to message encryption while you’re at it.</p> <h4>2. Network Resilience — support offline messaging so peers can come and go</h4> <p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p> <blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote> <p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href="https://en.wikipedia.org/wiki/Kademlia">Kademlia</a>-based <a href="https://en.wikipedia.org/wiki/Distributed_hash_table">distributed hash table</a> (DHT) <a href="https://github.com/libp2p/go-libp2p-kad-dht">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href="https://en.wikipedia.org/wiki/Hash_table">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href="https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png" width="800" height="485"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href="https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476"><em>this thesis</em></a><em>.</em></center></p> <p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href="https://github.com/libp2p/notes/issues/2#issuecomment-433729343">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href="https://github.com/textileio/textile-go/projects/2">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p> <h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4> <p>Ok, so our next requirement and its associated solution have received a <a href="https://github.com/ipfs/research-CRDT/">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href="https://github.com/orbitdb/ipfs-log">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href="https://www.atlassian.com/git/tutorials/using-branches/git-merge">3-way and fast-forward merged</a>.</p> <p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href="https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object">git hash tree</a>, <a href="https://twitter.com/Textile01/status/1004436869734543360">Merkle DAG</a>, or even a <a href="https://en.wikipedia.org/wiki/Blockchain">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p> <blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote> <p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p> <p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p> <p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png" width="800" height="478"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p> <p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p> <p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png" width="800" height="473"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p> <p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p> <h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4> <p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href="https://medium.com/textileio">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href="https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638">this previous article</a>).</p> <p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href="https://en.wikipedia.org/wiki/Fingerprint_%28computing%29">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href="https://github.com/ipld/cid"><em>self-describing </em>content-addressed identifier</a> (<a href="https://github.com/multiformats/multihash">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">cryptographic hash function</a> (like <a href="https://en.wikipedia.org/wiki/SHA-3">SHA</a>). For example, when the (unencrypted) <a href="https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p> <p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href="https://github.com/textileio/textile-go">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p> <h3>What’s Next?</h3> <p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p> <p><a href="https://twitter.com/Textile01/lists/textile-team/members">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href="https://github.com/textileio/textile-go/projects/2">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p> <p>If you are interested in learning more about this stuff, reach out over <a href="https://twitter.com/Textile01">Twitter</a> or <a href="https://slack.textile.io/">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href="https://github.com/textileio">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href="https://www.textile.photos/#cta">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p> </html> |
| title | Hi, Steemit! We're Textile. Here's a deeper look at the tech behind our Threads protocol. |
| author | sanderpick |
| permlink | hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol |
| json metadata | {"tags":["introduceyourself","ipfs","libp2p","filecoin","mobile"],"image":["https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png","https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif","https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png","https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png","https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png","https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png"],"links":["https://medium.com/@carsonfarmer","https://medium.com/@sanderpick","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14","https://realm.io/","https://firebase.google.com/","https://textile.photos/","https://en.wikipedia.org/wiki/Bitcoin","https://bitcoin.org/en/developer-guide#initial-block-download","https://filecoin.io/","https://en.wikipedia.org/wiki/Peer-to-peer","https://en.wikipedia.org/wiki/Lists_of_network_protocols","https://libp2p.io/","https://ipfs.io/","https://github.com/libp2p/go-libp2p-secio","https://github.com/libp2p/go-stream-security","https://developers.google.com/protocol-buffers/","https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto","https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel","https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20","https://en.wikipedia.org/wiki/Kademlia","https://en.wikipedia.org/wiki/Distributed_hash_table","https://github.com/libp2p/go-libp2p-kad-dht","https://en.wikipedia.org/wiki/Hash_table","https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507","https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476","https://github.com/libp2p/notes/issues/2#issuecomment-433729343","https://github.com/textileio/textile-go/projects/2","https://github.com/ipfs/research-CRDT/","https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type","https://github.com/orbitdb/ipfs-log","https://www.atlassian.com/git/tutorials/using-branches/git-merge","https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object","https://twitter.com/Textile01/status/1004436869734543360","https://en.wikipedia.org/wiki/Blockchain","https://medium.com/textileio","https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638","https://en.wikipedia.org/wiki/Fingerprint_%28computing%29","https://github.com/ipld/cid","https://github.com/multiformats/multihash","https://en.wikipedia.org/wiki/Cryptographic_hash_function","https://en.wikipedia.org/wiki/SHA-3","https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png","https://github.com/textileio/textile-go","https://twitter.com/Textile01/lists/textile-team/members","https://twitter.com/Textile01","https://slack.textile.io/","https://github.com/textileio","https://www.textile.photos/#cta"],"app":"steemit/0.1","format":"html"} |
| parent author | |
| parent permlink | introduceyourself |
| Transaction Info | Block #27329275/Trx fd70e3b34685167fa0c758f29486715f2170f7f3 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "<html>\n<p><em>Written by </em><a href=\"https://medium.com/@carsonfarmer\"><em>Carson Farmer</em></a><em> & </em><a href=\"https://medium.com/@sanderpick\"><em>Sander Pick</em></a></p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\" width=\"800\" height=\"565\"/><em>Download the app, take a picture, share!</em></center></p>\n<p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p>\n<p><strong>Why we built it</strong><br>\nWe designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p>\n<p><strong>What makes Threads different</strong><br>\nThreads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p>\n<p><strong>Why Threads are exciting</strong><br>\nBecause photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p>\n<p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href=\"https://realm.io/\">Realm</a> or <a href=\"https://firebase.google.com/\">Firebase</a> but <em>without</em> the centralized server.</p>\n<p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p>\n<h3>The experience</h3>\n<p><a href=\"https://textile.photos/\">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\" width=\"400\" height=\"600\"/></center></p>\n<h3>Requirements</h3>\n<p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p>\n<ol>\n <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br>\nTo enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li>\n <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br>\nSince we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li>\n <li><strong>A way to avoid state conflicts with other members of the group</strong><br>\nOn top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li>\n <li><strong>A mechanism to recover the full state from the network as a whole</strong><br>\nAnother important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href=\"https://en.wikipedia.org/wiki/Bitcoin\">Bitcoin</a>, where new nodes are able to <a href=\"https://bitcoin.org/en/developer-guide#initial-block-download\">download the full blockchain</a> from any connected peer.</li>\n <li><strong>Way to link updates via their content, rather than where they are stored</strong><br>\nSince we are building on top of the IPFS network, and would like to eventually support a <a href=\"https://filecoin.io/\">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li>\n</ol>\n<p>With these requirements in mind, let’s break down our solutions into their individual components…</p>\n<h3>Solutions</h3>\n<h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4>\n<p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href=\"https://en.wikipedia.org/wiki/Peer-to-peer\">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href=\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p>\n<p>At Textile, we use the super amazing <a href=\"https://libp2p.io/\">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href=\"https://ipfs.io/\">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\" width=\"800\" height=\"316\"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p>\n<blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote>\n<p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href=\"https://github.com/libp2p/go-libp2p-secio\">secio</a> <a href=\"https://github.com/libp2p/go-stream-security\">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p>\n<p>Like many IPFS-based projects, Textile uses <a href=\"https://developers.google.com/protocol-buffers/\">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">this previous article</a>. You can also check out how we structure our <a href=\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\">Protobuf messages</a>, learn a bit more about <a href=\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\">how secio works</a>, plus check out some <a href=\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\">recent updates</a> to message encryption while you’re at it.</p>\n<h4>2. Network Resilience — support offline messaging so peers can come and go</h4>\n<p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p>\n<blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote>\n<p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href=\"https://en.wikipedia.org/wiki/Kademlia\">Kademlia</a>-based <a href=\"https://en.wikipedia.org/wiki/Distributed_hash_table\">distributed hash table</a> (DHT) <a href=\"https://github.com/libp2p/go-libp2p-kad-dht\">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href=\"https://en.wikipedia.org/wiki/Hash_table\">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href=\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\" width=\"800\" height=\"485\"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href=\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\"><em>this thesis</em></a><em>.</em></center></p>\n<p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href=\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href=\"https://github.com/textileio/textile-go/projects/2\">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p>\n<h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4>\n<p>Ok, so our next requirement and its associated solution have received a <a href=\"https://github.com/ipfs/research-CRDT/\">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href=\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href=\"https://github.com/orbitdb/ipfs-log\">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href=\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\">3-way and fast-forward merged</a>.</p>\n<p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href=\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\">git hash tree</a>, <a href=\"https://twitter.com/Textile01/status/1004436869734543360\">Merkle DAG</a>, or even a <a href=\"https://en.wikipedia.org/wiki/Blockchain\">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p>\n<blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote>\n<p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p>\n<p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p>\n<p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\" width=\"800\" height=\"478\"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p>\n<p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p>\n<p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\" width=\"800\" height=\"473\"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p>\n<p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p>\n<h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4>\n<p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href=\"https://medium.com/textileio\">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href=\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\">this previous article</a>).</p>\n<p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href=\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href=\"https://github.com/ipld/cid\"><em>self-describing </em>content-addressed identifier</a> (<a href=\"https://github.com/multiformats/multihash\">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href=\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\">cryptographic hash function</a> (like <a href=\"https://en.wikipedia.org/wiki/SHA-3\">SHA</a>). For example, when the (unencrypted) <a href=\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p>\n<p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href=\"https://github.com/textileio/textile-go\">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p>\n<h3>What’s Next?</h3>\n<p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p>\n<p><a href=\"https://twitter.com/Textile01/lists/textile-team/members\">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href=\"https://github.com/textileio/textile-go/projects/2\">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p>\n<p>If you are interested in learning more about this stuff, reach out over <a href=\"https://twitter.com/Textile01\">Twitter</a> or <a href=\"https://slack.textile.io/\">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href=\"https://github.com/textileio\">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href=\"https://www.textile.photos/#cta\">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p>\n</html>",
"title": "Hi, Steemit! We're Textile. Here's a deeper look at the tech behind our Threads protocol.",
"author": "sanderpick",
"permlink": "hi-steemit-we-re-textile-here-s-a-deeper-look-at-the-tech-behind-our-threads-protocol",
"json_metadata": "{\"tags\":[\"introduceyourself\",\"ipfs\",\"libp2p\",\"filecoin\",\"mobile\"],\"image\":[\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\",\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\",\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\",\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\",\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\",\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\"],\"links\":[\"https://medium.com/@carsonfarmer\",\"https://medium.com/@sanderpick\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\",\"https://realm.io/\",\"https://firebase.google.com/\",\"https://textile.photos/\",\"https://en.wikipedia.org/wiki/Bitcoin\",\"https://bitcoin.org/en/developer-guide#initial-block-download\",\"https://filecoin.io/\",\"https://en.wikipedia.org/wiki/Peer-to-peer\",\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\",\"https://libp2p.io/\",\"https://ipfs.io/\",\"https://github.com/libp2p/go-libp2p-secio\",\"https://github.com/libp2p/go-stream-security\",\"https://developers.google.com/protocol-buffers/\",\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\",\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\",\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\",\"https://en.wikipedia.org/wiki/Kademlia\",\"https://en.wikipedia.org/wiki/Distributed_hash_table\",\"https://github.com/libp2p/go-libp2p-kad-dht\",\"https://en.wikipedia.org/wiki/Hash_table\",\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\",\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\",\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\",\"https://github.com/textileio/textile-go/projects/2\",\"https://github.com/ipfs/research-CRDT/\",\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\",\"https://github.com/orbitdb/ipfs-log\",\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\",\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\",\"https://twitter.com/Textile01/status/1004436869734543360\",\"https://en.wikipedia.org/wiki/Blockchain\",\"https://medium.com/textileio\",\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\",\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\",\"https://github.com/ipld/cid\",\"https://github.com/multiformats/multihash\",\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\",\"https://en.wikipedia.org/wiki/SHA-3\",\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\",\"https://github.com/textileio/textile-go\",\"https://twitter.com/Textile01/lists/textile-team/members\",\"https://twitter.com/Textile01\",\"https://slack.textile.io/\",\"https://github.com/textileio\",\"https://www.textile.photos/#cta\"],\"app\":\"steemit/0.1\",\"format\":\"html\"}",
"parent_author": "",
"parent_permlink": "introduceyourself"
}
],
"block": 27329275,
"trx_id": "fd70e3b34685167fa0c758f29486715f2170f7f3",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:33:21",
"virtual_op": false,
"trx_in_block": 11
}blocktradesblockchain operation: transfer to vesting completed2018/11/01 21:29:24
blocktradesblockchain operation: transfer to vesting completed
2018/11/01 21:29:24
| to account | sanderpick |
| hive vested | 6.003 HIVE |
| from account | blocktrades |
| vesting shares received | 12106.267835 VESTS |
| Transaction Info | Block #27329196/Trx 9b95c5fec4a2bc49f4c7902bd3b1329bfda62d6b |
View Raw JSON Data
{
"op": [
"transfer_to_vesting_completed",
{
"to_account": "sanderpick",
"hive_vested": "6.003 HIVE",
"from_account": "blocktrades",
"vesting_shares_received": "12106.267835 VESTS"
}
],
"block": 27329196,
"trx_id": "9b95c5fec4a2bc49f4c7902bd3b1329bfda62d6b",
"op_in_trx": 1,
"timestamp": "2018-11-01T21:29:24",
"virtual_op": true,
"trx_in_block": 23
}blocktradespowered up 6.003 HIVE to @sanderpick2018/11/01 21:29:24
blocktradespowered up 6.003 HIVE to @sanderpick
2018/11/01 21:29:24
| to | sanderpick |
| from | blocktrades |
| amount | 6.003 HIVE |
| Transaction Info | Block #27329196/Trx 9b95c5fec4a2bc49f4c7902bd3b1329bfda62d6b |
View Raw JSON Data
{
"op": [
"transfer_to_vesting",
{
"to": "sanderpick",
"from": "blocktrades",
"amount": "6.003 HIVE"
}
],
"block": 27329196,
"trx_id": "9b95c5fec4a2bc49f4c7902bd3b1329bfda62d6b",
"op_in_trx": 0,
"timestamp": "2018-11-01T21:29:24",
"virtual_op": false,
"trx_in_block": 23
}blocktradesblockchain operation: transfer to vesting completed2018/11/01 20:54:03
blocktradesblockchain operation: transfer to vesting completed
2018/11/01 20:54:03
| to account | sanderpick |
| hive vested | 5.885 HIVE |
| from account | blocktrades |
| vesting shares received | 11868.312524 VESTS |
| Transaction Info | Block #27328489/Trx 1500dddac4e02593504a0d402b441c25fcb7fbde |
View Raw JSON Data
{
"op": [
"transfer_to_vesting_completed",
{
"to_account": "sanderpick",
"hive_vested": "5.885 HIVE",
"from_account": "blocktrades",
"vesting_shares_received": "11868.312524 VESTS"
}
],
"block": 27328489,
"trx_id": "1500dddac4e02593504a0d402b441c25fcb7fbde",
"op_in_trx": 1,
"timestamp": "2018-11-01T20:54:03",
"virtual_op": true,
"trx_in_block": 5
}blocktradespowered up 5.885 HIVE to @sanderpick2018/11/01 20:54:03
blocktradespowered up 5.885 HIVE to @sanderpick
2018/11/01 20:54:03
| to | sanderpick |
| from | blocktrades |
| amount | 5.885 HIVE |
| Transaction Info | Block #27328489/Trx 1500dddac4e02593504a0d402b441c25fcb7fbde |
View Raw JSON Data
{
"op": [
"transfer_to_vesting",
{
"to": "sanderpick",
"from": "blocktrades",
"amount": "5.885 HIVE"
}
],
"block": 27328489,
"trx_id": "1500dddac4e02593504a0d402b441c25fcb7fbde",
"op_in_trx": 0,
"timestamp": "2018-11-01T20:54:03",
"virtual_op": false,
"trx_in_block": 5
}alphaboteffective vote applied for @sanderpick / textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol2018/11/01 16:45:27
alphaboteffective vote applied for @sanderpick / textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol
2018/11/01 16:45:27
| voter | alphabot |
| author | sanderpick |
| weight | 950 (9.50%) |
| rshares | 31247304 |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| pending payout | 0.000 HBD |
| total vote weight | 23271 |
| Transaction Info | Block #27323518/Trx b454fda4b9b3fe49a37f5746c6113227be91ecbd |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "alphabot",
"author": "sanderpick",
"weight": 950,
"rshares": 31247304,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"pending_payout": "0.000 HBD",
"total_vote_weight": 23271
}
],
"block": 27323518,
"trx_id": "b454fda4b9b3fe49a37f5746c6113227be91ecbd",
"op_in_trx": 1,
"timestamp": "2018-11-01T16:45:27",
"virtual_op": true,
"trx_in_block": 8
}2018/11/01 16:45:27
2018/11/01 16:45:27
| voter | alphabot |
| author | sanderpick |
| weight | 100 (1.00%) |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27323518/Trx b454fda4b9b3fe49a37f5746c6113227be91ecbd |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "alphabot",
"author": "sanderpick",
"weight": 100,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27323518,
"trx_id": "b454fda4b9b3fe49a37f5746c6113227be91ecbd",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:45:27",
"virtual_op": false,
"trx_in_block": 8
}2018/11/01 16:45:15
2018/11/01 16:45:15
| body | [Empty] |
| title | [Deleted] |
| author | sanderpick |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| json metadata | {"tags":["mobile"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | mobile |
| Transaction Info | Block #27323514/Trx 5ccb9f14d684c94581aaef41e1e357c1dff83b1b |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "[Empty]",
"title": "[Deleted]",
"author": "sanderpick",
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"json_metadata": "{\"tags\":[\"mobile\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "mobile"
}
],
"block": 27323514,
"trx_id": "5ccb9f14d684c94581aaef41e1e357c1dff83b1b",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:45:15",
"virtual_op": false,
"trx_in_block": 12
}2018/11/01 16:37:45
2018/11/01 16:37:45
| voter | fastresteem |
| author | sanderpick |
| weight | 448 (4.48%) |
| rshares | 30423177 |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| pending payout | 0.000 HBD |
| total vote weight | 22317 |
| Transaction Info | Block #27323364/Trx 52ead77f557e4010ea4f3345ed1058e84313af01 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "fastresteem",
"author": "sanderpick",
"weight": 448,
"rshares": 30423177,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"pending_payout": "0.000 HBD",
"total_vote_weight": 22317
}
],
"block": 27323364,
"trx_id": "52ead77f557e4010ea4f3345ed1058e84313af01",
"op_in_trx": 1,
"timestamp": "2018-11-01T16:37:45",
"virtual_op": true,
"trx_in_block": 31
}2018/11/01 16:37:45
2018/11/01 16:37:45
| voter | fastresteem |
| author | sanderpick |
| weight | 100 (1.00%) |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27323364/Trx 52ead77f557e4010ea4f3345ed1058e84313af01 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "fastresteem",
"author": "sanderpick",
"weight": 100,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27323364,
"trx_id": "52ead77f557e4010ea4f3345ed1058e84313af01",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:37:45",
"virtual_op": false,
"trx_in_block": 31
}2018/11/01 16:37:36
2018/11/01 16:37:36
| body | <html> <p><em>Written by </em><a href="https://medium.com/@carsonfarmer"><em>Carson Farmer</em></a><em> & </em><a href="https://medium.com/@sanderpick"><em>Sander Pick</em></a></p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png" width="800" height="565"/><em>Download the app, take a picture, share!</em></center></p> <p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p> <p><strong>Why we built it</strong><br> We designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p> <p><strong>What makes Threads different</strong><br> Threads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p> <p><strong>Why Threads are exciting</strong><br> Because photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p> <p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href="https://realm.io/">Realm</a> or <a href="https://firebase.google.com/">Firebase</a> but <em>without</em> the centralized server.</p> <p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p> <h3>The experience</h3> <p><a href="https://textile.photos/">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif" width="400" height="600"/></center></p> <h3>Requirements</h3> <p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p> <ol> <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br> To enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li> <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br> Since we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li> <li><strong>A way to avoid state conflicts with other members of the group</strong><br> On top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li> <li><strong>A mechanism to recover the full state from the network as a whole</strong><br> Another important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href="https://en.wikipedia.org/wiki/Bitcoin">Bitcoin</a>, where new nodes are able to <a href="https://bitcoin.org/en/developer-guide#initial-block-download">download the full blockchain</a> from any connected peer.</li> <li><strong>Way to link updates via their content, rather than where they are stored</strong><br> Since we are building on top of the IPFS network, and would like to eventually support a <a href="https://filecoin.io/">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li> </ol> <p>With these requirements in mind, let’s break down our solutions into their individual components…</p> <h3>Solutions</h3> <h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4> <p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href="https://en.wikipedia.org/wiki/Peer-to-peer">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href="https://en.wikipedia.org/wiki/Lists_of_network_protocols">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p> <p>At Textile, we use the super amazing <a href="https://libp2p.io/">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href="https://ipfs.io/">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png" width="800" height="316"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p> <blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote> <p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href="https://github.com/libp2p/go-libp2p-secio">secio</a> <a href="https://github.com/libp2p/go-stream-security">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p> <p>Like many IPFS-based projects, Textile uses <a href="https://developers.google.com/protocol-buffers/">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">this previous article</a>. You can also check out how we structure our <a href="https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto">Protobuf messages</a>, learn a bit more about <a href="https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel">how secio works</a>, plus check out some <a href="https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca">recent updates</a> to message encryption while you’re at it.</p> <h4>2. Network Resilience — support offline messaging so peers can come and go</h4> <p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p> <blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote> <p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href="https://en.wikipedia.org/wiki/Kademlia">Kademlia</a>-based <a href="https://en.wikipedia.org/wiki/Distributed_hash_table">distributed hash table</a> (DHT) <a href="https://github.com/libp2p/go-libp2p-kad-dht">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href="https://en.wikipedia.org/wiki/Hash_table">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href="https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png" width="800" height="485"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href="https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476"><em>this thesis</em></a><em>.</em></center></p> <p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href="https://github.com/libp2p/notes/issues/2#issuecomment-433729343">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href="https://github.com/textileio/textile-go/projects/2">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p> <h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4> <p>Ok, so our next requirement and its associated solution have received a <a href="https://github.com/ipfs/research-CRDT/">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href="https://github.com/orbitdb/ipfs-log">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href="https://www.atlassian.com/git/tutorials/using-branches/git-merge">3-way and fast-forward merged</a>.</p> <p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href="https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object">git hash tree</a>, <a href="https://twitter.com/Textile01/status/1004436869734543360">Merkle DAG</a>, or even a <a href="https://en.wikipedia.org/wiki/Blockchain">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p> <blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote> <p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p> <p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p> <p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png" width="800" height="478"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p> <p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p> <p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png" width="800" height="473"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p> <p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p> <h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4> <p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href="https://medium.com/textileio">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href="https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638">this previous article</a>).</p> <p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href="https://en.wikipedia.org/wiki/Fingerprint_%28computing%29">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href="https://github.com/ipld/cid"><em>self-describing </em>content-addressed identifier</a> (<a href="https://github.com/multiformats/multihash">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">cryptographic hash function</a> (like <a href="https://en.wikipedia.org/wiki/SHA-3">SHA</a>). For example, when the (unencrypted) <a href="https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p> <p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href="https://github.com/textileio/textile-go">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p> <h3>What’s Next?</h3> <p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p> <p><a href="https://twitter.com/Textile01/lists/textile-team/members">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href="https://github.com/textileio/textile-go/projects/2">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p> <p>If you are interested in learning more about this stuff, reach out over <a href="https://twitter.com/Textile01">Twitter</a> or <a href="https://slack.textile.io/">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href="https://github.com/textileio">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href="https://www.textile.photos/#cta">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p> </html> |
| title | Textile Threads whitepaper… just kidding… a deeper look at the tech behind Textile’s Threads protocol |
| author | sanderpick |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| json metadata | {"tags":["decentralization","p2p","crypto","blockchain","mobile"],"image":["https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png","https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif","https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png","https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png","https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png","https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png"],"links":["https://medium.com/@carsonfarmer","https://medium.com/@sanderpick","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14","https://realm.io/","https://firebase.google.com/","https://textile.photos/","https://en.wikipedia.org/wiki/Bitcoin","https://bitcoin.org/en/developer-guide#initial-block-download","https://filecoin.io/","https://en.wikipedia.org/wiki/Peer-to-peer","https://en.wikipedia.org/wiki/Lists_of_network_protocols","https://libp2p.io/","https://ipfs.io/","https://github.com/libp2p/go-libp2p-secio","https://github.com/libp2p/go-stream-security","https://developers.google.com/protocol-buffers/","https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto","https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel","https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20","https://en.wikipedia.org/wiki/Kademlia","https://en.wikipedia.org/wiki/Distributed_hash_table","https://github.com/libp2p/go-libp2p-kad-dht","https://en.wikipedia.org/wiki/Hash_table","https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507","https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476","https://github.com/libp2p/notes/issues/2#issuecomment-433729343","https://github.com/textileio/textile-go/projects/2","https://github.com/ipfs/research-CRDT/","https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type","https://github.com/orbitdb/ipfs-log","https://www.atlassian.com/git/tutorials/using-branches/git-merge","https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object","https://twitter.com/Textile01/status/1004436869734543360","https://en.wikipedia.org/wiki/Blockchain","https://medium.com/textileio","https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638","https://en.wikipedia.org/wiki/Fingerprint_%28computing%29","https://github.com/ipld/cid","https://github.com/multiformats/multihash","https://en.wikipedia.org/wiki/Cryptographic_hash_function","https://en.wikipedia.org/wiki/SHA-3","https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png","https://github.com/textileio/textile-go","https://twitter.com/Textile01/lists/textile-team/members","https://twitter.com/Textile01","https://slack.textile.io/","https://github.com/textileio","https://www.textile.photos/#cta"],"app":"steemit/0.1","format":"html"} |
| parent author | |
| parent permlink | mobile |
| Transaction Info | Block #27323361/Trx cc5a1c09f224dae0c0de896393c778b894bd8fca |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "<html>\n<p><em>Written by </em><a href=\"https://medium.com/@carsonfarmer\"><em>Carson Farmer</em></a><em> & </em><a href=\"https://medium.com/@sanderpick\"><em>Sander Pick</em></a></p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\" width=\"800\" height=\"565\"/><em>Download the app, take a picture, share!</em></center></p>\n<p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p>\n<p><strong>Why we built it</strong><br>\nWe designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p>\n<p><strong>What makes Threads different</strong><br>\nThreads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p>\n<p><strong>Why Threads are exciting</strong><br>\nBecause photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p>\n<p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href=\"https://realm.io/\">Realm</a> or <a href=\"https://firebase.google.com/\">Firebase</a> but <em>without</em> the centralized server.</p>\n<p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p>\n<h3>The experience</h3>\n<p><a href=\"https://textile.photos/\">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\" width=\"400\" height=\"600\"/></center></p>\n<h3>Requirements</h3>\n<p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p>\n<ol>\n <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br>\nTo enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li>\n <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br>\nSince we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li>\n <li><strong>A way to avoid state conflicts with other members of the group</strong><br>\nOn top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li>\n <li><strong>A mechanism to recover the full state from the network as a whole</strong><br>\nAnother important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href=\"https://en.wikipedia.org/wiki/Bitcoin\">Bitcoin</a>, where new nodes are able to <a href=\"https://bitcoin.org/en/developer-guide#initial-block-download\">download the full blockchain</a> from any connected peer.</li>\n <li><strong>Way to link updates via their content, rather than where they are stored</strong><br>\nSince we are building on top of the IPFS network, and would like to eventually support a <a href=\"https://filecoin.io/\">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li>\n</ol>\n<p>With these requirements in mind, let’s break down our solutions into their individual components…</p>\n<h3>Solutions</h3>\n<h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4>\n<p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href=\"https://en.wikipedia.org/wiki/Peer-to-peer\">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href=\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p>\n<p>At Textile, we use the super amazing <a href=\"https://libp2p.io/\">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href=\"https://ipfs.io/\">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\" width=\"800\" height=\"316\"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p>\n<blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote>\n<p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href=\"https://github.com/libp2p/go-libp2p-secio\">secio</a> <a href=\"https://github.com/libp2p/go-stream-security\">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p>\n<p>Like many IPFS-based projects, Textile uses <a href=\"https://developers.google.com/protocol-buffers/\">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">this previous article</a>. You can also check out how we structure our <a href=\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\">Protobuf messages</a>, learn a bit more about <a href=\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\">how secio works</a>, plus check out some <a href=\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\">recent updates</a> to message encryption while you’re at it.</p>\n<h4>2. Network Resilience — support offline messaging so peers can come and go</h4>\n<p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p>\n<blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote>\n<p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href=\"https://en.wikipedia.org/wiki/Kademlia\">Kademlia</a>-based <a href=\"https://en.wikipedia.org/wiki/Distributed_hash_table\">distributed hash table</a> (DHT) <a href=\"https://github.com/libp2p/go-libp2p-kad-dht\">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href=\"https://en.wikipedia.org/wiki/Hash_table\">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href=\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\" width=\"800\" height=\"485\"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href=\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\"><em>this thesis</em></a><em>.</em></center></p>\n<p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href=\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href=\"https://github.com/textileio/textile-go/projects/2\">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p>\n<h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4>\n<p>Ok, so our next requirement and its associated solution have received a <a href=\"https://github.com/ipfs/research-CRDT/\">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href=\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href=\"https://github.com/orbitdb/ipfs-log\">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href=\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\">3-way and fast-forward merged</a>.</p>\n<p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href=\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\">git hash tree</a>, <a href=\"https://twitter.com/Textile01/status/1004436869734543360\">Merkle DAG</a>, or even a <a href=\"https://en.wikipedia.org/wiki/Blockchain\">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p>\n<blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote>\n<p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p>\n<p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p>\n<p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\" width=\"800\" height=\"478\"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p>\n<p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p>\n<p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\" width=\"800\" height=\"473\"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p>\n<p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p>\n<h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4>\n<p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href=\"https://medium.com/textileio\">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href=\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\">this previous article</a>).</p>\n<p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href=\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href=\"https://github.com/ipld/cid\"><em>self-describing </em>content-addressed identifier</a> (<a href=\"https://github.com/multiformats/multihash\">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href=\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\">cryptographic hash function</a> (like <a href=\"https://en.wikipedia.org/wiki/SHA-3\">SHA</a>). For example, when the (unencrypted) <a href=\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p>\n<p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href=\"https://github.com/textileio/textile-go\">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p>\n<h3>What’s Next?</h3>\n<p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p>\n<p><a href=\"https://twitter.com/Textile01/lists/textile-team/members\">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href=\"https://github.com/textileio/textile-go/projects/2\">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p>\n<p>If you are interested in learning more about this stuff, reach out over <a href=\"https://twitter.com/Textile01\">Twitter</a> or <a href=\"https://slack.textile.io/\">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href=\"https://github.com/textileio\">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href=\"https://www.textile.photos/#cta\">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p>\n</html>",
"title": "Textile Threads whitepaper… just kidding… a deeper look at the tech behind Textile’s Threads protocol",
"author": "sanderpick",
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"json_metadata": "{\"tags\":[\"decentralization\",\"p2p\",\"crypto\",\"blockchain\",\"mobile\"],\"image\":[\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\",\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\",\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\",\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\",\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\",\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\"],\"links\":[\"https://medium.com/@carsonfarmer\",\"https://medium.com/@sanderpick\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\",\"https://realm.io/\",\"https://firebase.google.com/\",\"https://textile.photos/\",\"https://en.wikipedia.org/wiki/Bitcoin\",\"https://bitcoin.org/en/developer-guide#initial-block-download\",\"https://filecoin.io/\",\"https://en.wikipedia.org/wiki/Peer-to-peer\",\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\",\"https://libp2p.io/\",\"https://ipfs.io/\",\"https://github.com/libp2p/go-libp2p-secio\",\"https://github.com/libp2p/go-stream-security\",\"https://developers.google.com/protocol-buffers/\",\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\",\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\",\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\",\"https://en.wikipedia.org/wiki/Kademlia\",\"https://en.wikipedia.org/wiki/Distributed_hash_table\",\"https://github.com/libp2p/go-libp2p-kad-dht\",\"https://en.wikipedia.org/wiki/Hash_table\",\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\",\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\",\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\",\"https://github.com/textileio/textile-go/projects/2\",\"https://github.com/ipfs/research-CRDT/\",\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\",\"https://github.com/orbitdb/ipfs-log\",\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\",\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\",\"https://twitter.com/Textile01/status/1004436869734543360\",\"https://en.wikipedia.org/wiki/Blockchain\",\"https://medium.com/textileio\",\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\",\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\",\"https://github.com/ipld/cid\",\"https://github.com/multiformats/multihash\",\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\",\"https://en.wikipedia.org/wiki/SHA-3\",\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\",\"https://github.com/textileio/textile-go\",\"https://twitter.com/Textile01/lists/textile-team/members\",\"https://twitter.com/Textile01\",\"https://slack.textile.io/\",\"https://github.com/textileio\",\"https://www.textile.photos/#cta\"],\"app\":\"steemit/0.1\",\"format\":\"html\"}",
"parent_author": "",
"parent_permlink": "mobile"
}
],
"block": 27323361,
"trx_id": "cc5a1c09f224dae0c0de896393c778b894bd8fca",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:37:36",
"virtual_op": false,
"trx_in_block": 9
}2018/11/01 16:35:45
2018/11/01 16:35:45
| voter | andrewxhill |
| author | sanderpick |
| weight | 1631 (16.31%) |
| rshares | 152762950 |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| pending payout | 0.000 HBD |
| total vote weight | 21389 |
| Transaction Info | Block #27323324/Trx 010d81815711283224501047e25064466a676724 |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "andrewxhill",
"author": "sanderpick",
"weight": 1631,
"rshares": 152762950,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"pending_payout": "0.000 HBD",
"total_vote_weight": 21389
}
],
"block": 27323324,
"trx_id": "010d81815711283224501047e25064466a676724",
"op_in_trx": 1,
"timestamp": "2018-11-01T16:35:45",
"virtual_op": true,
"trx_in_block": 29
}2018/11/01 16:35:45
2018/11/01 16:35:45
| voter | andrewxhill |
| author | sanderpick |
| weight | 10000 (100.00%) |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27323324/Trx 010d81815711283224501047e25064466a676724 |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "andrewxhill",
"author": "sanderpick",
"weight": 10000,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27323324,
"trx_id": "010d81815711283224501047e25064466a676724",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:35:45",
"virtual_op": false,
"trx_in_block": 29
}2018/11/01 16:30:45
2018/11/01 16:30:45
| body | Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in: https://medium.com/textileio/wip-textile-threads-whitepaper-just-kidding-6ce3a6624338 |
| title | |
| author | cheetah |
| permlink | cheetah-re-sanderpicktextile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| json metadata | |
| parent author | sanderpick |
| parent permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27323224/Trx c0e5a992846d1346ed92138de94bb266e51bd1d5 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:\nhttps://medium.com/textileio/wip-textile-threads-whitepaper-just-kidding-6ce3a6624338",
"title": "",
"author": "cheetah",
"permlink": "cheetah-re-sanderpicktextile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"json_metadata": "",
"parent_author": "sanderpick",
"parent_permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27323224,
"trx_id": "c0e5a992846d1346ed92138de94bb266e51bd1d5",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:30:45",
"virtual_op": false,
"trx_in_block": 2
}cheetaheffective vote applied for @sanderpick / textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol2018/11/01 16:30:39
cheetaheffective vote applied for @sanderpick / textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol
2018/11/01 16:30:39
| voter | cheetah |
| author | sanderpick |
| weight | 167 (1.67%) |
| rshares | 279678612 |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| pending payout | 0.000 HBD |
| total vote weight | 16727 |
| Transaction Info | Block #27323222/Trx 73b04dde954bafb42766967e8db1bb4e4a50833f |
View Raw JSON Data
{
"op": [
"effective_comment_vote",
{
"voter": "cheetah",
"author": "sanderpick",
"weight": 167,
"rshares": 279678612,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"pending_payout": "0.000 HBD",
"total_vote_weight": 16727
}
],
"block": 27323222,
"trx_id": "73b04dde954bafb42766967e8db1bb4e4a50833f",
"op_in_trx": 1,
"timestamp": "2018-11-01T16:30:39",
"virtual_op": true,
"trx_in_block": 22
}2018/11/01 16:30:39
2018/11/01 16:30:39
| voter | cheetah |
| author | sanderpick |
| weight | 8 (0.08%) |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| Transaction Info | Block #27323222/Trx 73b04dde954bafb42766967e8db1bb4e4a50833f |
View Raw JSON Data
{
"op": [
"vote",
{
"voter": "cheetah",
"author": "sanderpick",
"weight": 8,
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol"
}
],
"block": 27323222,
"trx_id": "73b04dde954bafb42766967e8db1bb4e4a50833f",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:30:39",
"virtual_op": false,
"trx_in_block": 22
}2018/11/01 16:30:30
2018/11/01 16:30:30
| body | <html> <p><em>Written by </em><a href="https://medium.com/@carsonfarmer"><em>Carson Farmer</em></a><em> & </em><a href="https://medium.com/@sanderpick"><em>Sander Pick</em></a></p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png" width="800" height="565"/><em>Download the app, take a picture, share!</em></center></p> <p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p> <p><strong>Why we built it</strong><br> We designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p> <p><strong>What makes Threads different</strong><br> Threads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p> <p><strong>Why Threads are exciting</strong><br> Because photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p> <p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href="https://realm.io/">Realm</a> or <a href="https://firebase.google.com/">Firebase</a> but <em>without</em> the centralized server.</p> <p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p> <h3>The experience</h3> <p><a href="https://textile.photos/">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif" width="400" height="600"/></center></p> <h3>Requirements</h3> <p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p> <ol> <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br> To enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li> <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br> Since we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li> <li><strong>A way to avoid state conflicts with other members of the group</strong><br> On top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li> <li><strong>A mechanism to recover the full state from the network as a whole</strong><br> Another important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href="https://en.wikipedia.org/wiki/Bitcoin">Bitcoin</a>, where new nodes are able to <a href="https://bitcoin.org/en/developer-guide#initial-block-download">download the full blockchain</a> from any connected peer.</li> <li><strong>Way to link updates via their content, rather than where they are stored</strong><br> Since we are building on top of the IPFS network, and would like to eventually support a <a href="https://filecoin.io/">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li> </ol> <p>With these requirements in mind, let’s break down our solutions into their individual components…</p> <h3>Solutions</h3> <h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4> <p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href="https://en.wikipedia.org/wiki/Peer-to-peer">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href="https://en.wikipedia.org/wiki/Lists_of_network_protocols">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p> <p>At Textile, we use the super amazing <a href="https://libp2p.io/">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href="https://ipfs.io/">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png" width="800" height="316"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p> <blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote> <p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href="https://github.com/libp2p/go-libp2p-secio">secio</a> <a href="https://github.com/libp2p/go-stream-security">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p> <p>Like many IPFS-based projects, Textile uses <a href="https://developers.google.com/protocol-buffers/">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14">this previous article</a>. You can also check out how we structure our <a href="https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto">Protobuf messages</a>, learn a bit more about <a href="https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel">how secio works</a>, plus check out some <a href="https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca">recent updates</a> to message encryption while you’re at it.</p> <h4>2. Network Resilience — support offline messaging so peers can come and go</h4> <p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p> <blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote> <p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href="https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href="https://en.wikipedia.org/wiki/Kademlia">Kademlia</a>-based <a href="https://en.wikipedia.org/wiki/Distributed_hash_table">distributed hash table</a> (DHT) <a href="https://github.com/libp2p/go-libp2p-kad-dht">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href="https://en.wikipedia.org/wiki/Hash_table">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href="https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png" width="800" height="485"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href="https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476"><em>this thesis</em></a><em>.</em></center></p> <p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href="https://github.com/libp2p/notes/issues/2#issuecomment-433729343">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href="https://github.com/textileio/textile-go/projects/2">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p> <h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4> <p>Ok, so our next requirement and its associated solution have received a <a href="https://github.com/ipfs/research-CRDT/">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href="https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href="https://github.com/orbitdb/ipfs-log">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href="https://www.atlassian.com/git/tutorials/using-branches/git-merge">3-way and fast-forward merged</a>.</p> <p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href="https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object">git hash tree</a>, <a href="https://twitter.com/Textile01/status/1004436869734543360">Merkle DAG</a>, or even a <a href="https://en.wikipedia.org/wiki/Blockchain">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p> <blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote> <p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p> <p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p> <p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png" width="800" height="478"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p> <p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p> <p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p> <p><center><img src="https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png" width="800" height="473"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p> <p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p> <h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4> <p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href="https://medium.com/textileio">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href="https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638">this previous article</a>).</p> <p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href="https://en.wikipedia.org/wiki/Fingerprint_%28computing%29">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href="https://github.com/ipld/cid"><em>self-describing </em>content-addressed identifier</a> (<a href="https://github.com/multiformats/multihash">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">cryptographic hash function</a> (like <a href="https://en.wikipedia.org/wiki/SHA-3">SHA</a>). For example, when the (unencrypted) <a href="https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p> <p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href="https://github.com/textileio/textile-go">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p> <h3>What’s Next?</h3> <p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p> <p><a href="https://twitter.com/Textile01/lists/textile-team/members">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href="https://github.com/textileio/textile-go/projects/2">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p> <p>If you are interested in learning more about this stuff, reach out over <a href="https://twitter.com/Textile01">Twitter</a> or <a href="https://slack.textile.io/">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href="https://github.com/textileio">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href="https://www.textile.photos/#cta">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p> </html> |
| title | Textile Threads whitepaper… just kidding… a deeper look at the tech behind Textile’s Threads protocol |
| author | sanderpick |
| permlink | textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol |
| json metadata | {"tags":["mobile","p2p","crypto","blockchain","decentralization"],"image":["https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png","https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif","https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png","https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png","https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png","https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png"],"links":["https://medium.com/@carsonfarmer","https://medium.com/@sanderpick","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14","https://realm.io/","https://firebase.google.com/","https://textile.photos/","https://en.wikipedia.org/wiki/Bitcoin","https://bitcoin.org/en/developer-guide#initial-block-download","https://filecoin.io/","https://en.wikipedia.org/wiki/Peer-to-peer","https://en.wikipedia.org/wiki/Lists_of_network_protocols","https://libp2p.io/","https://ipfs.io/","https://github.com/libp2p/go-libp2p-secio","https://github.com/libp2p/go-stream-security","https://developers.google.com/protocol-buffers/","https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto","https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel","https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca","https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20","https://en.wikipedia.org/wiki/Kademlia","https://en.wikipedia.org/wiki/Distributed_hash_table","https://github.com/libp2p/go-libp2p-kad-dht","https://en.wikipedia.org/wiki/Hash_table","https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507","https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476","https://github.com/libp2p/notes/issues/2#issuecomment-433729343","https://github.com/textileio/textile-go/projects/2","https://github.com/ipfs/research-CRDT/","https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type","https://github.com/orbitdb/ipfs-log","https://www.atlassian.com/git/tutorials/using-branches/git-merge","https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object","https://twitter.com/Textile01/status/1004436869734543360","https://en.wikipedia.org/wiki/Blockchain","https://medium.com/textileio","https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638","https://en.wikipedia.org/wiki/Fingerprint_%28computing%29","https://github.com/ipld/cid","https://github.com/multiformats/multihash","https://en.wikipedia.org/wiki/Cryptographic_hash_function","https://en.wikipedia.org/wiki/SHA-3","https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png","https://github.com/textileio/textile-go","https://twitter.com/Textile01/lists/textile-team/members","https://twitter.com/Textile01","https://slack.textile.io/","https://github.com/textileio","https://www.textile.photos/#cta"],"app":"steemit/0.1","format":"html"} |
| parent author | |
| parent permlink | mobile |
| Transaction Info | Block #27323219/Trx 1b0fac506cdfb108f722b2e1c911db0abc02ff43 |
View Raw JSON Data
{
"op": [
"comment",
{
"body": "<html>\n<p><em>Written by </em><a href=\"https://medium.com/@carsonfarmer\"><em>Carson Farmer</em></a><em> & </em><a href=\"https://medium.com/@sanderpick\"><em>Sander Pick</em></a></p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\" width=\"800\" height=\"565\"/><em>Download the app, take a picture, share!</em></center></p>\n<p>Recently, we’ve started writing more about the technologies underlying Textile Photos that help keep your photos (and likes and comments, etc) safe and secure on the decentralized web. In our previous post, we talked about <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">the encryption process Textile Photos</a>, with a focus how Textile delivers end-to-end encrypted photo sharing. Today’s post is a follow-up (though it should also be sufficiently detailed to stand on its own), this time highlighting how Textile coordinates private photo sharing among groups of users, a feature we call <em>Threads</em>.</p>\n<p><strong>Why we built it</strong><br>\nWe designed Threads to allow groups of users to share photos securely and privately, without any centralized, authoritative database. We also made sure it all works well offline, that its possible to recover lost data, and that its easy to add new members.</p>\n<p><strong>What makes Threads different</strong><br>\nThreads allow private groups to post photos and interact over a decentralized network, maintaining complete control over their own content. Textile operates in a completely zero-knowledge framework. Private by design.</p>\n<p><strong>Why Threads are exciting</strong><br>\nBecause photos are just the first step. Today, Threads allow users to share a photo with other Thread in a secure, decentralized way. Threads can facilitate secure sharing, coordination, and storage of <em>many</em> types of data over a decentralized network. Upgradable by design.</p>\n<p>On the surface, you can think of each Thread like a decentralized database, shared between specific participants. We built Threads into the fabric of Textile (see what we did there 😉) because group members need a record of who shared what photo, and when. But, once we created Threads, we realized just how powerful a concept this was — for those familiar with mobile app development, think <a href=\"https://realm.io/\">Realm</a> or <a href=\"https://firebase.google.com/\">Firebase</a> but <em>without</em> the centralized server.</p>\n<p>To really understand what Threads brings to the table, you really need to understand Threads themselves. So let’s dig a bit deeper into how Textile conceptualizes and implements Threads, and how that helps keep your photos (and likes and comments, etc) safe and secure on the decentralized web. We’ll start by highlighting the specific requirements we had when developing Threads, and then break down each of these requirements into the specific solutions that we came up with. Along the way, our CTO Sander Pick will highlight how those various solutions came about, and why we think our approach is in the best interest of our users.</p>\n<h3>The experience</h3>\n<p><a href=\"https://textile.photos/\">Textile Photos</a> allows small, decentralized, private groups to share photos, send messages, and engage with each other. That’s the experience, so it has to ‘just work’.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\" width=\"400\" height=\"600\"/></center></p>\n<h3>Requirements</h3>\n<p>To drive the Textile user experience, we identified five key features needed in the sharing protocols:</p>\n<ol>\n <li><strong>A mechanism to share and receive state updates within a group of n users</strong><br>\nTo enable photo sharing (and other common interactions such as likes and comments) among a group of friends and/or colleagues, some concept of a <em>shared state</em> is required.</li>\n <li><strong>A way to ensure the shared state stays resilient to peers dropping out or latency issues</strong><br>\nSince we’re operating in a mobile environment, we have to expect peers to continually drop ‘offline’ due to coverage issues, app back-grounding, battery optimizations, and a whole slew of other reasons for a mobile device to be cut off from a network.</li>\n <li><strong>A way to avoid state conflicts with other members of the group</strong><br>\nOn top of the requirements above, when peers do come back online, we don’t want any state changes that were made by other members of the group while they were disconnected from the network to conflict with their own local changes.</li>\n <li><strong>A mechanism to recover the full state from the network as a whole</strong><br>\nAnother important consideration in the mobile world is that the number of users (out of n) that are online at any given time is generally unknown, and quite possibly zero. To reiterate, we want a decentralized shared state, but it has to work <em>even when you are the only member online</em>. This means we have to assume the full group state may not ever be directly accessible (i.e., downloadable) from a single group member. This is in contrast to something like <a href=\"https://en.wikipedia.org/wiki/Bitcoin\">Bitcoin</a>, where new nodes are able to <a href=\"https://bitcoin.org/en/developer-guide#initial-block-download\">download the full blockchain</a> from any connected peer.</li>\n <li><strong>Way to link updates via their content, rather than where they are stored</strong><br>\nSince we are building on top of the IPFS network, and would like to eventually support a <a href=\"https://filecoin.io/\">Filecoin</a>-based future in which users can select from a multitude of decentralized storage providers, Threads need to embrace content addressing, rather than location addressing. This makes it easy to grow and change the underlying network, without affecting data access and sharing.</li>\n</ol>\n<p>With these requirements in mind, let’s break down our solutions into their individual components…</p>\n<h3>Solutions</h3>\n<h4>1. Handling Updates — use a peer-to-peer network with structured updates</h4>\n<p>First things first: <em>how do we handle state updates between a set of distributed peers?</em> This is mostly about <a href=\"https://en.wikipedia.org/wiki/Peer-to-peer\">peer-to-peer (p2p) networking</a>. And when it comes to communicating between heterogeneous network devices (computers, phones, IoT devices, etc), we actually need many <a href=\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\">different types of network protocols</a>. That way, no matter what type of device we are talking about — be it a phone, desktop computer, browser, or Internet-enabled fridge — it is able to communicate with other devices located in the same room, or on the other side of the planet.</p>\n<p>At Textile, we use the super amazing <a href=\"https://libp2p.io/\">libp2p</a> library for our networking needs. Libp2p is a networking stack and library (you might have heard it called a protocol suite) modularized out of the <a href=\"https://ipfs.io/\">IPFS project</a>, and bundled separately for other tools to use. Essentially, libp2p does all the heavy network lifting so that we can focus on our core task: exchanging updates between communicating peers.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\" width=\"800\" height=\"316\"/><em>Decentralized, peer-to-peer networks are radically different types of communication networks.</em></center></p>\n<blockquote>Libp2p was a pretty natural choice for us. The stack includes all the crypto and networking protocols we need to deliver messages to group members, and the libp2p developer community is super responsive and excited about the power of p2p interactions. Easy choice.</blockquote>\n<p>The other really nice thing about using the libp2p library is it comes packed with many useful cryptography tools and functions, keeping communications secure. For instance, all p2p communications over the Textile network use the <a href=\"https://github.com/libp2p/go-libp2p-secio\">secio</a> <a href=\"https://github.com/libp2p/go-stream-security\">stream security transport</a>. This way, all connections use secure sessions provided by libp2p/secio to encrypt all traffic, whereby a TLS-like handshake is used to setup the initial communication channel.</p>\n<p>Like many IPFS-based projects, Textile uses <a href=\"https://developers.google.com/protocol-buffers/\">Protocol Buffers</a> for over-the-wire communication, and advanced cryptographic algorithms to secure those messages. Essentially, each update to the shared group state is just an encrypted Profobuf message with two parts: a header with author and date info, and a body with the type-specific data. These pieces are sent in their own inner-’envelope’ which contains a link to the encrypted message and the Thread ID. This inner-envelope is then signed by the sender and placed into the wire ‘envelope’ along with it’s signature. You can read more about some of the cryptographic tools Textile uses in <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\">this previous article</a>. You can also check out how we structure our <a href=\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\">Protobuf messages</a>, learn a bit more about <a href=\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\">how secio works</a>, plus check out some <a href=\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\">recent updates</a> to message encryption while you’re at it.</p>\n<h4>2. Network Resilience — support offline messaging so peers can come and go</h4>\n<p>If you are at all familiar with libp2p, then you might be thinking <em>“ah libp2p has a pubsub layer that would be perfect for exchanging updates to a group of connecting peers”</em>. And while you’d certainly be right, there are a few key limitations that makes using pubsub for something like Textile Photos pretty cumbersome. On top of this, while pubsub is super nice for things like chat rooms or distributed services, it is a ‘fire-and-forget’ messaging protocol, meaning that once a peer publishes a message, it is up to its peers to ensure they are listening for the right message at the right time. To circumvent this, some pubsub systems introduce message echoing, to ensure a message stays in the system long enough to be picked up by the peers who might need it. However, this can lead to really noisy network traffic, and is really just a band-aid over a larger issue.</p>\n<blockquote>Our initial POC involved pubsub and always-online room echoers… not scalable or particularly decentralized. A real solution to distributing state has to involve direct messaging with an offline mechanism.</blockquote>\n<p>So this starts to get at our second requirement, that <em>the shared state stays resilient to peers dropping out</em>. We need to assume peers might not be around to receive important messages in ‘real-time’, which is a common problem with p2p systems. Right now, Textile addresses this problem by enabling what you might call <em>offline messaging</em>. Since we’re <a href=\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\">already using IPFS for data storage and communication</a>, we wanted to take advantage of some of the core technologies driving IPFS. In particular, we (currently) use a special fork of the <a href=\"https://en.wikipedia.org/wiki/Kademlia\">Kademlia</a>-based <a href=\"https://en.wikipedia.org/wiki/Distributed_hash_table\">distributed hash table</a> (DHT) <a href=\"https://github.com/libp2p/go-libp2p-kad-dht\">used by IPFS, </a>that allows us to post messages for a peer directly in the DHT. For those unfamiliar with DHTs, they are a <a href=\"https://en.wikipedia.org/wiki/Hash_table\">hash table</a> where the data is spread across a network of nodes or peers. And these peers are all coordinated to enable efficient access and lookup between nodes in a decentralized way. You can read more about this kind of stuff in our previous article about <a href=\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\">how IPFS peers find, request, and retrieve content (and each other) on the decentralized web</a>. So, when a peer we want to communicate with is offline, rather than blindly sending them a message that will never be received, we post a message to Textile’s DHT, and they can then retrieve that message the next time they come online again. Conceptually simple, and works pretty well in practice.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\" width=\"800\" height=\"485\"/><em>p2p network with custom Textile DHT overlay. Peers post (key, value) messages (value) with a key specific to their intended recipient, and this key is broadcast and available to entire network; though only the intended recipient is able to decrypt the actual message content. Based on Figure 1–2 from </em><a href=\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\"><em>this thesis</em></a><em>.</em></center></p>\n<p>There are still some issues with our current approach, including that it is difficult/impossible to remove messages from the DHT manually. Indeed, it can start to get a bit messy when left-over offline messages have to be retrieved each time a peer comes back online… imagine a peer that goes in and out of service frequently, this could lead to a lot of network traffic and wasted CPU cycles. So, we’ve <a href=\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\">implemented an alternative</a> to this DHT-based offline messaging system that does not suffer from these limitations (and also allows us to participate in the public IPFS network), while still remaining decentralized and scalable in the long-term. This new approach should be released soon, after more testing and evaluation. You can follow along with this progress as part of the <a href=\"https://github.com/textileio/textile-go/projects/2\">move towards a Cafe-based setup</a> (see also <strong>What’s Next</strong>).</p>\n<h4>3 & 4. Avoiding Conflicts & State Recovery —use a CRDT to keep an immutable history across peers</h4>\n<p>Ok, so our next requirement and its associated solution have received a <a href=\"https://github.com/ipfs/research-CRDT/\">great deal of research and development attention over the years</a>. The question of “<em>how to avoid state conflicts with other members of a group?”</em> comes up when working collaboratively on documents, updating shared databases, etc. For the purposes of updating a shared Thread of photos, it turns out that an <a href=\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\">operation-based CRDT</a> that supports append-only operations is pretty much all you need to get going. You can think of Textile’s CRDT (which shares some ideas with <a href=\"https://github.com/orbitdb/ipfs-log\">ipfs-log</a>) setup as an immutable, append-only tree that can be used to model a mutable, shared state between peers. Every entry in the tree is saved on IPFS, and each points to a hash of previous entry(ies) forming a graph. These trees can be <a href=\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\">3-way and fast-forward merged</a>.</p>\n<p>Speaking of forks and joins, for those familiar with git and other similar system, you might be thinking this sounds a lot like a <a href=\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\">git hash tree</a>, <a href=\"https://twitter.com/Textile01/status/1004436869734543360\">Merkle DAG</a>, or even a <a href=\"https://en.wikipedia.org/wiki/Blockchain\">blockchain</a>. And you’d be right! The concepts are very similar, and this buys us some really nice properties for building and maintaining a shared state. By modeling our shared Thread state in this way, we benefit from tried and tested methods for allowing a peer to incorporate other peers’ updates into their state while maintaining history (via fast-forwards and three-way merging for example).</p>\n<blockquote>At the end of the day, a Thread is just a git-like hash tree of updates with a deterministic merge policy. Simple.</blockquote>\n<p>So what does this look like in practice? Currently — because things might change as we make improvements to the underlying implementation — each Thread in Textile Photos is essentially a chain of updates, where each update represents some specific action or event. For instance, when you create a new Thread, under-the-hood you are actually creating a <code>JOIN</code> update on a new Thread chain. Similarly, when you update the Thread via a new photo (<code>DATA</code> update), comment, or like (<code>ANNOTATION</code> update), you’re actually updating that Thread chain. After each modification, the <code>HEAD</code> of the Thread will point to the latest update.</p>\n<p>Building on top of these ideas, we also have concepts such as an <code>INVITE</code>, which points a new peer to a given point on the Thread chain, or a <code>MERGE</code>, which happens when the current <code>HEAD</code> is not contained in an incoming update’s parent list for some reason (maybe the peer doesn’t know about it because they were offline). If two peers are merging the <em>same sub trees</em>, all they need to do to ensure the update resolves to the same hash is a) include the same date b) exclude author info. To get the same date, they both follow a rule: choose the latest of the parents for the date (in practice they add a little bit extra on to keep it ahead of both parents).</p>\n<p>To give you a better idea of what exactly we’re talking about, consider the following set of operations: <code>User A</code> creates a new Thread, and adds a Photo. They then externally invite <code>User B</code> (sent via some other <em>secure</em> communication channel), who eventually joins the Thread. But before <code>User B</code> is able to join the Thread, <code>User A</code> adds another Photo, moving the Thread’s <code>HEAD</code> forward. By the time <code>User B</code> joins the Thread, they’d end up with a Thread sequence that looks something like this:</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\" width=\"800\" height=\"478\"/><em>Thread join example. Solid arrows point towards the ‘parent’ of a given update, over-the-wire communications are indicated with a 📶-style arrow, and messages that are rebroadcast (e.g., via the welcome message) are indicated with a dashed arrow. Similarly, merges point to both their parent updates.</em></center></p>\n<p>Here, we see the merge happening at the end of the sequence because the bottom peer is joining via an external invite that is no longer <code>HEAD</code> , forcing them to merge the most recent <code>DATA</code> update with their own <code>JOIN</code> update. But since merge results are deterministic (given the same parents), both peers create the <code>MERGE</code> update locally, and do not broadcast them to avoid trading merges back and forth.</p>\n<p>A more complete sequence is given in the following figure. Suppose <code>User A</code> goes ‘offline’ (e.g., their phone goes to sleep, they shut down the app, they lose their data connection, etc), and in the mean time, both<code> Users A</code> and <code>B</code> update the Thread, with <code>User A</code> adding an <code>ANNOTATION</code> update, and <code>User B</code> adding a new Photo (<code>DATA</code> update). Now, when <code>User A</code> comes back online, there is a conflict, and both Users create a <code>MERGE</code> update to remedy this. A <code>MERGE</code> update has two parents, in this case, the <code>DATA</code> and <code>ANNOTATION</code> update from the different users. As always, the <code>HEAD</code> continues to point to the latest update (which in the example below eventually becomes an <code>ANNOTATION</code> from <code>User B</code>). Once both peers are online again, the more straightforward update and transmit mode of operation can continue.</p>\n<p><center><img src=\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\" width=\"800\" height=\"473\"/><em>More complex Thread interaction where one or more peers are temporarily offline. Note that an external invite is the same as a normal invite, but the invite details are encrypted with a single use key, which is sharable with the invite update location.</em></center></p>\n<p>The same properties that make hash trees or blockchains useful for developing a shared, consistent (consensus-driven) state, also makes it possible to address our fourth requirement: <em>the ability to recover the full state from the network as a whole</em>. Because each Thread update references its parent(s), given a single point on the Thread chain, we can trace back all the way to the beginning of the Thread. For example, at any point along the sequence in the above figures, a peer can trace back the history of the Thread, as indicated by the solid arrows. This works particularly nicely when a peer <code>JOIN</code>s a thread, even at a point prior to the current <code>HEAD</code>. They can simply <code>JOIN</code>, and any existing Thread member can send them the latest <code>HEAD</code> (even via offline messages if needed). From here, they can explore the entire history of the Thread with ease. This is all really similar to git commit speak, in which one only needs to know about a single commit to be able to trace back the entire history of a code project; it’s also essentially how blockchains work.</p>\n<h4>5. Content Addressing — store everything on IPFS and get ready to scale</h4>\n<p>As we alluded to earlier, each update to a Thread is backed by an IPFS CID hash (i.e., they are content addressable chunks of data on IPFS). This means <em>where</em> the data is stored is no longer relevant… IPFS will find it on the network via it’s hash. This helps us address our fifth requirement, that we have a <em>way to link updates via their content, rather than where they are stored</em>. We’ve covered this topic a lot <a href=\"https://medium.com/textileio\">in the past</a>, but for the uninitiated, the next paragraph provides a summary of how content addressing on IPFS works (pulled from <a href=\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\">this previous article</a>).</p>\n<p>Rather than referencing a file or chunk of data by its location (think HTTP), we reference it via its <a href=\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\">fingerprint</a>. In IPFS and other such systems, this means identifying content by its cryptographic hash, or even better, a <a href=\"https://github.com/ipld/cid\"><em>self-describing </em>content-addressed identifier</a> (<a href=\"https://github.com/multiformats/multihash\">multihash</a>). A cryptographic hash is a (relatively) short alphanumeric string that’s calculated by running your content through a <a href=\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\">cryptographic hash function</a> (like <a href=\"https://en.wikipedia.org/wiki/SHA-3\">SHA</a>). For example, when the (unencrypted) <a href=\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\">Textile logo</a> is added to IPFS, its multihash ends up being <code>QmbgGgWW3vH7v9FDxVCzcouKGChqGEjtf6YLDUgSHnk5J2</code>. This ‘hash’ is actually the CID (Content IDentifier) for that file, computed from the <em>raw data </em>within that PNG. It is guaranteed to be cryptographically unique to the contents of <em>that </em>file, and that file only. If we change that file by even one bit, the hash will become something completely different.</p>\n<p>Now, when we want to access a file over IPFS (like the above logo), we can simply ask the IPFS network for the file with that exact CID, the network will find the peers that have the data (using a DHT), retrieve it, and verify (using the CID) that it’s the correct file. What this means is we can technically get the file from <em>multiple </em>places because as long as the file matches the hash, we know we’re getting the right data. Which brings us to the solution to our final requirement… use IPFS! For now, Textile is maintaining a network of large, homogeneous, volunteer nodes (we call them <a href=\"https://github.com/textileio/textile-go\">Cafe</a>s) to ‘pin’ and store content on IPFS. It is important to note here that the other nodes doing the pinning are the same as the nodes on your phone — Textile Nodes that offer a pinning service to other peers. Soon, we’ll allow users to elect their own Cafe nodes, add even add additional nodes for redundancy. All this could eventually be driven by Filecoin for even greater scalablility and flexibility.</p>\n<h3>What’s Next?</h3>\n<p>So there you have it. Five solutions to five requirements for seamless, secure, decentralized photo sharing and backup. Easy 😉. And at a conceptual level, the Textile Thread protocol <em>is</em> relatively simple: blocks of operations chained together to produce a beautiful Thread of photos. But there’s a lot of complexity going on under-the-hood that has required a lot of experimentation, testing, and limit pushing, especially on mobile. And our journey isn’t over yet.</p>\n<p><a href=\"https://twitter.com/Textile01/lists/textile-team/members\">The Textile team</a> is still hard at work iterating, updating, and improving upon what we already have working. For example, we’ll soon to moving to a new offline messaging system that allow us to drop the custom DHT fork, and move back to the public IPFS network. On top of this, our <a href=\"https://github.com/textileio/textile-go/projects/2\">move to more powerful backup</a> and recovery capabilities has us taking new approaches to security, profile management, offline interactions, and much much more. On top of these changes, the team is actively working to modularize the Threads concept and code into its own stand-alone package, which should provide developers with something akin to a Realm and/or Firebase layer for decentralized mobile applications!</p>\n<p>If you are interested in learning more about this stuff, reach out over <a href=\"https://twitter.com/Textile01\">Twitter</a> or <a href=\"https://slack.textile.io/\">Slack</a>, or pull us aside the next time you see us at a conference or event. We’re happy to provide background, thoughts, and opinions on how we think the future of decentralized apps will play out. In the mean time, don’t forget to check out our <a href=\"https://github.com/textileio\">GitHub repos</a> for code and PRs that showcase our current and old implementations. We try to make sure all our development happens out in the open, so you can see things as they develop. Additionally, if you haven’t already, <a href=\"https://www.textile.photos/#cta\">don’t miss out on signing up for our waitlist</a>, where you can get early access to Textile Photos, the beautiful interface to Textile’s Threads.</p>\n</html>",
"title": "Textile Threads whitepaper… just kidding… a deeper look at the tech behind Textile’s Threads protocol",
"author": "sanderpick",
"permlink": "textile-threads-whitepaper-just-kidding-a-deeper-look-at-the-tech-behind-textile-s-threads-protocol",
"json_metadata": "{\"tags\":[\"mobile\",\"p2p\",\"crypto\",\"blockchain\",\"decentralization\"],\"image\":[\"https://cdn-images-1.medium.com/max/800/1*beyK889WqmNEatN55oBAnA.png\",\"https://cdn-images-1.medium.com/max/800/1*xEE3Pp-LdU_r8qsPvMVS8w.gif\",\"https://cdn-images-1.medium.com/max/800/1*0iCOZW6HrpdQ0izG5O9ciw.png\",\"https://cdn-images-1.medium.com/max/800/1*JsXZAveyMui2izg_-FMh0g.png\",\"https://cdn-images-1.medium.com/max/800/1*h9eNwOauuT7mMeS4o-yGbw.png\",\"https://cdn-images-1.medium.com/max/800/1*3EuEtFHqUtALTczIeZ8_eg.png\"],\"links\":[\"https://medium.com/@carsonfarmer\",\"https://medium.com/@sanderpick\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14\",\"https://realm.io/\",\"https://firebase.google.com/\",\"https://textile.photos/\",\"https://en.wikipedia.org/wiki/Bitcoin\",\"https://bitcoin.org/en/developer-guide#initial-block-download\",\"https://filecoin.io/\",\"https://en.wikipedia.org/wiki/Peer-to-peer\",\"https://en.wikipedia.org/wiki/Lists_of_network_protocols\",\"https://libp2p.io/\",\"https://ipfs.io/\",\"https://github.com/libp2p/go-libp2p-secio\",\"https://github.com/libp2p/go-stream-security\",\"https://developers.google.com/protocol-buffers/\",\"https://github.com/textileio/textile-go/blob/master/pb/protos/thread.proto\",\"https://github.com/auditdrivencrypto/secure-channel/blob/master/prior-art.md#ipfss-secure-channel\",\"https://github.com/textileio/textile-go/commit/05a269cd5dd72da224c4d2c472abad00a078d4ca\",\"https://medium.com/textileio/the-5-steps-to-end-to-end-encrypted-photo-storage-and-sharing-45ad4aad6b14#ea20\",\"https://en.wikipedia.org/wiki/Kademlia\",\"https://en.wikipedia.org/wiki/Distributed_hash_table\",\"https://github.com/libp2p/go-libp2p-kad-dht\",\"https://en.wikipedia.org/wiki/Hash_table\",\"https://medium.com/textileio/swapping-bits-and-distributing-hashes-on-the-decentralized-web-5da98a3507\",\"https://www.researchgate.net/figure/2-Overlay-and-underlay-view-of-Distributed-Hash-Tables-11_fig2_304495476\",\"https://github.com/libp2p/notes/issues/2#issuecomment-433729343\",\"https://github.com/textileio/textile-go/projects/2\",\"https://github.com/ipfs/research-CRDT/\",\"https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type\",\"https://github.com/orbitdb/ipfs-log\",\"https://www.atlassian.com/git/tutorials/using-branches/git-merge\",\"https://blog.thoughtram.io/git/2014/11/18/the-anatomy-of-a-git-commit.html#meet-the-tree-object\",\"https://twitter.com/Textile01/status/1004436869734543360\",\"https://en.wikipedia.org/wiki/Blockchain\",\"https://medium.com/textileio\",\"https://medium.com/textileio/enabling-the-distributed-web-abf7ab33b638\",\"https://en.wikipedia.org/wiki/Fingerprint_%28computing%29\",\"https://github.com/ipld/cid\",\"https://github.com/multiformats/multihash\",\"https://en.wikipedia.org/wiki/Cryptographic_hash_function\",\"https://en.wikipedia.org/wiki/SHA-3\",\"https://ipfs.io/ipfs/QmcNzGMQ1hUzSnqM2aH1Um8KWLRC4Rd9TyY4kFuYZXWXaF/Textile_Icon_500x500.png\",\"https://github.com/textileio/textile-go\",\"https://twitter.com/Textile01/lists/textile-team/members\",\"https://twitter.com/Textile01\",\"https://slack.textile.io/\",\"https://github.com/textileio\",\"https://www.textile.photos/#cta\"],\"app\":\"steemit/0.1\",\"format\":\"html\"}",
"parent_author": "",
"parent_permlink": "mobile"
}
],
"block": 27323219,
"trx_id": "1b0fac506cdfb108f722b2e1c911db0abc02ff43",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:30:30",
"virtual_op": false,
"trx_in_block": 28
}blocktradesblockchain operation: transfer to vesting completed2018/11/01 16:28:30
blocktradesblockchain operation: transfer to vesting completed
2018/11/01 16:28:30
| to account | sanderpick |
| hive vested | 11.739 HIVE |
| from account | blocktrades |
| vesting shares received | 23674.341006 VESTS |
| Transaction Info | Block #27323179/Trx bbfaddccabdd48377726866da7c7493be2eb6bbc |
View Raw JSON Data
{
"op": [
"transfer_to_vesting_completed",
{
"to_account": "sanderpick",
"hive_vested": "11.739 HIVE",
"from_account": "blocktrades",
"vesting_shares_received": "23674.341006 VESTS"
}
],
"block": 27323179,
"trx_id": "bbfaddccabdd48377726866da7c7493be2eb6bbc",
"op_in_trx": 1,
"timestamp": "2018-11-01T16:28:30",
"virtual_op": true,
"trx_in_block": 6
}blocktradespowered up 11.739 HIVE to @sanderpick2018/11/01 16:28:30
blocktradespowered up 11.739 HIVE to @sanderpick
2018/11/01 16:28:30
| to | sanderpick |
| from | blocktrades |
| amount | 11.739 HIVE |
| Transaction Info | Block #27323179/Trx bbfaddccabdd48377726866da7c7493be2eb6bbc |
View Raw JSON Data
{
"op": [
"transfer_to_vesting",
{
"to": "sanderpick",
"from": "blocktrades",
"amount": "11.739 HIVE"
}
],
"block": 27323179,
"trx_id": "bbfaddccabdd48377726866da7c7493be2eb6bbc",
"op_in_trx": 0,
"timestamp": "2018-11-01T16:28:30",
"virtual_op": false,
"trx_in_block": 6
}sanderpickupdated their account properties2018/11/01 15:23:57
sanderpickupdated their account properties
2018/11/01 15:23:57
| account | sanderpick |
| memo key | STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3 |
| json metadata | {"profile":{"profile_image":"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg","cover_image":"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5","name":"Sander Pick","about":"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.","location":"Sunnyvale, CA","website":"https://www.textile.io"}} |
| Transaction Info | Block #27321889/Trx 598440f34f030f630a06d9526dc248337a0d5a30 |
View Raw JSON Data
{
"op": [
"account_update",
{
"account": "sanderpick",
"memo_key": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3",
"json_metadata": "{\"profile\":{\"profile_image\":\"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg\",\"cover_image\":\"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5\",\"name\":\"Sander Pick\",\"about\":\"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.\",\"location\":\"Sunnyvale, CA\",\"website\":\"https://www.textile.io\"}}"
}
],
"block": 27321889,
"trx_id": "598440f34f030f630a06d9526dc248337a0d5a30",
"op_in_trx": 0,
"timestamp": "2018-11-01T15:23:57",
"virtual_op": false,
"trx_in_block": 29
}steemdelegated 6.136 HP to @sanderpick2018/06/01 19:12:09
steemdelegated 6.136 HP to @sanderpick
2018/06/01 19:12:09
| delegatee | sanderpick |
| delegator | steem |
| vesting shares | 9961.473965 VESTS |
| Transaction Info | Block #22948884/Trx b0c163e4190864f4e94ce580e2cac26b3b4a9b52 |
View Raw JSON Data
{
"op": [
"delegate_vesting_shares",
{
"delegatee": "sanderpick",
"delegator": "steem",
"vesting_shares": "9961.473965 VESTS"
}
],
"block": 22948884,
"trx_id": "b0c163e4190864f4e94ce580e2cac26b3b4a9b52",
"op_in_trx": 0,
"timestamp": "2018-06-01T19:12:09",
"virtual_op": false,
"trx_in_block": 49
}steemdelegated 18.750 HP to @sanderpick2018/03/02 18:42:51
steemdelegated 18.750 HP to @sanderpick
2018/03/02 18:42:51
| delegatee | sanderpick |
| delegator | steem |
| vesting shares | 30438.477360 VESTS |
| Transaction Info | Block #20330247/Trx 09355c3a99cb6535147741c6b9d40edfb78482b6 |
View Raw JSON Data
{
"op": [
"delegate_vesting_shares",
{
"delegatee": "sanderpick",
"delegator": "steem",
"vesting_shares": "30438.477360 VESTS"
}
],
"block": 20330247,
"trx_id": "09355c3a99cb6535147741c6b9d40edfb78482b6",
"op_in_trx": 4,
"timestamp": "2018-03-02T18:42:51",
"virtual_op": false,
"trx_in_block": 34
}sanderpickupdated their account properties2018/03/02 17:55:57
sanderpickupdated their account properties
2018/03/02 17:55:57
| account | sanderpick |
| memo key | STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3 |
| json metadata | {"profile":{"profile_image":"https://pbs.twimg.com/profile_images/840041206000246785/He5ot_rH_400x400.jpg","cover_image":"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5","name":"Sander Pick","about":"I like hard problems, focus, open spaces, cryptography, decentralization, rock climbing. Montanan.","location":"Sunnyvale, CA","website":"https://www.textile.io"}} |
| Transaction Info | Block #20329309/Trx 6cc448eb84ac0fabc51308c7bf7df7aa1e788853 |
View Raw JSON Data
{
"op": [
"account_update",
{
"account": "sanderpick",
"memo_key": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3",
"json_metadata": "{\"profile\":{\"profile_image\":\"https://pbs.twimg.com/profile_images/840041206000246785/He5ot_rH_400x400.jpg\",\"cover_image\":\"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5\",\"name\":\"Sander Pick\",\"about\":\"I like hard problems, focus, open spaces, cryptography, decentralization, rock climbing. Montanan.\",\"location\":\"Sunnyvale, CA\",\"website\":\"https://www.textile.io\"}}"
}
],
"block": 20329309,
"trx_id": "6cc448eb84ac0fabc51308c7bf7df7aa1e788853",
"op_in_trx": 0,
"timestamp": "2018-03-02T17:55:57",
"virtual_op": false,
"trx_in_block": 31
}sanderpickupdated their account properties2018/03/02 17:50:21
sanderpickupdated their account properties
2018/03/02 17:50:21
| account | sanderpick |
| memo key | STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3 |
| json metadata | {"profile":{"profile_image":"https://ipfs.textile.io/ipfs/QmUZ4RbHCahBsfjJWGJhee8u7o8wuhTzhabJMnhik7TwQ3","cover_image":"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5","name":"Sander Pick","about":"I like hard problems, focus, open spaces, cryptography, decentralization, rock climbing. Montanan.","location":"Sunnyvale, CA","website":"https://www.textile.io"}} |
| Transaction Info | Block #20329197/Trx 1fd600b64ea5ee1e65f46b4a84334ee44f96f40e |
View Raw JSON Data
{
"op": [
"account_update",
{
"account": "sanderpick",
"memo_key": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3",
"json_metadata": "{\"profile\":{\"profile_image\":\"https://ipfs.textile.io/ipfs/QmUZ4RbHCahBsfjJWGJhee8u7o8wuhTzhabJMnhik7TwQ3\",\"cover_image\":\"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5\",\"name\":\"Sander Pick\",\"about\":\"I like hard problems, focus, open spaces, cryptography, decentralization, rock climbing. Montanan.\",\"location\":\"Sunnyvale, CA\",\"website\":\"https://www.textile.io\"}}"
}
],
"block": 20329197,
"trx_id": "1fd600b64ea5ee1e65f46b4a84334ee44f96f40e",
"op_in_trx": 0,
"timestamp": "2018-03-02T17:50:21",
"virtual_op": false,
"trx_in_block": 45
}2018/03/02 17:19:30
2018/03/02 17:19:30
| creator | steem |
| new account name | sanderpick |
| initial delegation | 30690.000000 VESTS |
| initial vesting shares | 204.285596 VESTS |
| Transaction Info | Block #20328580/Trx 4c8f11dfdc8cfa9a2e2861c32dd4dadf616a2073 |
View Raw JSON Data
{
"op": [
"account_created",
{
"creator": "steem",
"new_account_name": "sanderpick",
"initial_delegation": "30690.000000 VESTS",
"initial_vesting_shares": "204.285596 VESTS"
}
],
"block": 20328580,
"trx_id": "4c8f11dfdc8cfa9a2e2861c32dd4dadf616a2073",
"op_in_trx": 1,
"timestamp": "2018-03-02T17:19:30",
"virtual_op": true,
"trx_in_block": 6
}steemcreated a new account: @sanderpick2018/03/02 17:19:30
steemcreated a new account: @sanderpick
2018/03/02 17:19:30
| fee | 0.100 HIVE |
| owner | {"key_auths":[["STM5miWoPgXh9Coxgg462uDpuAuBuVa5jmGEEqCxFLCzxuWovxvhy",1]],"account_auths":[],"weight_threshold":1} |
| active | {"key_auths":[["STM87JJzyvJ9e8Rf6vm72fDaQtET6dKWPqHycSmfbvKziP4QpGHQw",1]],"account_auths":[],"weight_threshold":1} |
| creator | steem |
| posting | {"key_auths":[["STM6BTaccrcMfAkSe4csndTguQMXXD6EXtRXE6wSeFW5p67EcZi3k",1]],"account_auths":[],"weight_threshold":1} |
| memo key | STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3 |
| delegation | 30690.000000 VESTS |
| extensions | [] |
| json metadata | {} |
| new account name | sanderpick |
| Transaction Info | Block #20328580/Trx 4c8f11dfdc8cfa9a2e2861c32dd4dadf616a2073 |
View Raw JSON Data
{
"op": [
"account_create_with_delegation",
{
"fee": "0.100 HIVE",
"owner": {
"key_auths": [
[
"STM5miWoPgXh9Coxgg462uDpuAuBuVa5jmGEEqCxFLCzxuWovxvhy",
1
]
],
"account_auths": [],
"weight_threshold": 1
},
"active": {
"key_auths": [
[
"STM87JJzyvJ9e8Rf6vm72fDaQtET6dKWPqHycSmfbvKziP4QpGHQw",
1
]
],
"account_auths": [],
"weight_threshold": 1
},
"creator": "steem",
"posting": {
"key_auths": [
[
"STM6BTaccrcMfAkSe4csndTguQMXXD6EXtRXE6wSeFW5p67EcZi3k",
1
]
],
"account_auths": [],
"weight_threshold": 1
},
"memo_key": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3",
"delegation": "30690.000000 VESTS",
"extensions": [],
"json_metadata": "{}",
"new_account_name": "sanderpick"
}
],
"block": 20328580,
"trx_id": "4c8f11dfdc8cfa9a2e2861c32dd4dadf616a2073",
"op_in_trx": 0,
"timestamp": "2018-03-02T17:19:30",
"virtual_op": false,
"trx_in_block": 6
}Manabar
Voting Power100.00%
Downvote Power100.00%
Resource Credits100.00%
Reputation Progress0.00%
{
"voting_manabar": {
"current_mana": 45566893283,
"last_update_time": 1541115177
},
"downvote_manabar": {
"current_mana": 0,
"last_update_time": 1520011167
},
"rc_account": {
"account": "sanderpick",
"rc_manabar": {
"current_mana": 49873955934,
"last_update_time": 1543260072
},
"max_rc_creation_adjustment": {
"amount": "2020748973",
"precision": 6,
"nai": "@@000000037"
},
"max_rc": 49873955934,
"delegated_rc": 0,
"received_delegated_rc": 0
}
}Account Metadata
| POSTING JSON METADATA | |
| profile | {"profile_image":"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg","cover_image":"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5","name":"Sander Pick","about":"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.","location":"Sunnyvale, CA","website":"https://www.textile.io"} |
| JSON METADATA | |
| profile | {"profile_image":"https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg","cover_image":"https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5","name":"Sander Pick","about":"Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.","location":"Sunnyvale, CA","website":"https://www.textile.io"} |
{
"posting_json_metadata": {
"profile": {
"profile_image": "https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg",
"cover_image": "https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5",
"name": "Sander Pick",
"about": "Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.",
"location": "Sunnyvale, CA",
"website": "https://www.textile.io"
}
},
"json_metadata": {
"profile": {
"profile_image": "https://cdn.steemitimages.com/DQmcvgiUj7JsQ9yqVgDuehbAwiB7b5sokTH9YUvaMVzbBTz/IMG_8874.jpg",
"cover_image": "https://ipfs.textile.io/ipfs/QmbmwFiGQG4ZbzcervBRYiVMGqi56oWcSTcx2Do8YaAGZ5",
"name": "Sander Pick",
"about": "Co-founder/CTO @ https://textile.io . Having fun with p2p networks, cryptography, and rock climbing. Lover of free speech and the open marketplace of ideas.",
"location": "Sunnyvale, CA",
"website": "https://www.textile.io"
}
}
}Auth Keys
Owner
Single Signature
Public Keys
STM5miWoPgXh9Coxgg462uDpuAuBuVa5jmGEEqCxFLCzxuWovxvhy1/1
Active
Single Signature
Public Keys
STM87JJzyvJ9e8Rf6vm72fDaQtET6dKWPqHycSmfbvKziP4QpGHQw1/1
Posting
Single Signature
Public Keys
STM6BTaccrcMfAkSe4csndTguQMXXD6EXtRXE6wSeFW5p67EcZi3k1/1
Memo
STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3
{
"owner": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM5miWoPgXh9Coxgg462uDpuAuBuVa5jmGEEqCxFLCzxuWovxvhy",
1
]
]
},
"active": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM87JJzyvJ9e8Rf6vm72fDaQtET6dKWPqHycSmfbvKziP4QpGHQw",
1
]
]
},
"posting": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM6BTaccrcMfAkSe4csndTguQMXXD6EXtRXE6wSeFW5p67EcZi3k",
1
]
]
},
"memo": "STM5CZugansgXw2GtjWmHu9kiiPrpvpiGHNGGw9d4yeb97zVFsdw3"
}Witness Votes
0 / 30
No active witness votes.
[]