Skip to content

Instantly share code, notes, and snippets.

@thedod
Last active August 29, 2015 14:22
Show Gist options
  • Save thedod/7a4a81224b5bed676b00 to your computer and use it in GitHub Desktop.
Save thedod/7a4a81224b5bed676b00 to your computer and use it in GitHub Desktop.

We've moved HERE

[This obsolete gist stays here because stuff might still link here]


DarkenedAges — a twister-based game of intrigue and bad crypto

Trust ██ like █████. Be ███████.

TL;DR We play with partial leaks that look like this

  ██████ Alice, ███ ███████ ███ ███████ ███████ ████ ███████ legal ███████████ ███ delays ███ ██ serious problem
+ ██████ ██████ ███ doesn't get ███████ ███████ ████ ███████ legal ███████████ ███ ██████ ███ ██ ███████ problem
= ______________________________________________________________________________________________________________
  ██████ Alice, ███ doesn't get ███████ ███████ ████ ███████ legal ███████████ ███ delays ███ ██ serious problem
+ ...
= ______________________________________________________________________________________________________________
  Unlike Alice, Bob doesn't get dropped packets when pinging legal department, and delays are no serious problem

Slightly longer examples here and here.

For public moves and gossip, see #DarkenedAges on twister

In AD 2101, peace was beginning.

The Unigalitarian Church has abolished the internet. A source of a century of war, misfortune, and mistrust. Instead, people are only allowed to communicate via a church sanctioned telegram system.

How can they enforce this? How can the church be sure people aren't communicating via other means? For example a face to face talk between husband and wife, mother and daughter? They simply ask nicely.

Every citizen has to go to a daily confession where he/she is interviewed by a priest/priestes who are well equiped and well trained to detect lies and deviant behavior. It's a bit like the Voight Kampff empathy test ;)

It's not that you are not allowed to communicate by means other than official telegrams. It's just that you have to report all such communications. Other parties to such a conversation also have such duties, and are also being scrutinized daily under lie-detection gear and practices. You simply assume they know it all already, and try to be as percise as possible, because a contradiction might start a pretty nasty investigation and waste inquisitive resources.

Privacy (to a reasonable extent)

The church understands the value of privacy: there's no merit for the soul in coerced righteousness. If sin can't tempt you, how can you be a saint? This is why no one can read your telegrams, unless you're a suspect. If the court so orders, a telegram of yours might have to be exposed. In order to do that, 8 keys should combined in order to expose the message (although the game will probably start with 4 or even 3 keys until we have enough players). Each message is encrypted with 8 different keys. These keys are distributed to 8 random citizens called trustees. You too can become a trustee to one or more telegrams. If you're not rich or educated enough (or just lazy), the church can manage a key crypt in your behalf. This way, checks and balances are kept, and those who have nothing to hide have nothing to fear.

In the beginning, most people chose the easy options, and only deranged otaku teenagers bothered to manage one (It was easier for them. The Japanese manuals are the best. The English translation deliberately sucks).

Then came the attack on the El-Hamdan (EH) encryption [unofficially attributed to the late Frau Tse Tung, but you didn't hear it from me ;) ][1]

In AD 2102, peace was beginning to make sense.

As you already know, copies of all telegrams are kept on record at the Publicly Available International Archive (PAIA) [aka Leakvile :)] Now that EH encryption has been broken, It has created a booming black market. The currency is telegram keys (aka unredactions). Today, anyone who wants to make some pocket money (and who doesn't?) manages her own key crypt. They're no longer the bulky church-issued software. Systems today convert back and forth between El-Hamdan ciphers and keys and their more juicy conterparts: leakables and unredactions. The black market system where the more keys you own, the more of the actual telegram (or leakable) is exposed to you. You know the metadata of your adversaries. You know who they have been talking to. You might even be lucky enough to be a trustee to some of their communications.

Perhaps you could trade this information with their adversaries? The possibilities are endless.

How to play

More info soon, meanwhile here's how to become a player:

  • Every player should have a twister account
  • Fork this, add a line about yourself to players.csv, and mention @darkenedages on twister with a link to your forked gist. Bugger it. Just twist @darkenedages I want to play #DarkenedAges ;)
  • It is recommended to follow @darkenedages and have #DarkenedAges in your profile, but the formal definition of "player" is "one who appears at players.csv" ;)

Essentially index.html was produced with python darkened.py > index.html. The motivation behind this "full retard crypto suite" is to create an environment where everyone can have a go at code breaking.


[1] "It was never truly attributed to FTT, but it is believe that she has found the transformation between a 1/K El-Hamdan key and 1/K of the One Time Pad (OTP) equivalent of the session key when expanded (although she has failed to see that the data was chunked on a word boundary, as discovered by ████ 2 years earlier)." -- "Faster Than Thought", a biography of FTT by Jon Reinhardt

#!/usr/bin/env python3
import random
import time
import codecs
import json
import csv
import textwrap
CHAR_REDACTED = '\u2588' # Full block.
CHAR_CONFLICT = '\u2573' # Box drawings light diagonal cross.
NTRUSTEES = 3 # for now(?)
## [['an', 'msg', 'is', 'a', 'list', 'of', 'lines,'],
## ['where', 'each', 'line', 'is', 'a', 'list', 'of', 'words.']]
def str2msg(s):
"explode a string into an msg"
return list(map(lambda l:l.split(), s.splitlines()))
def msg2str(ws):
"implode an msg into a string"
return '\n'.join(map(lambda l:' '.join(l), ws))
def mapmsgs(func, *msgs):
"""map func word-per-word on all corresponding words inside [a sequence of] msgs of the same structure. E.g.
>>> mapmsgs(lambda word1, word2:'({}/{})'.format(word1, word2), str2msg("a b\nc d"), str2msg("x y\nz w"))
[['(a/x)', '(b/y)'], ['(c/z)', '(d/w)']]"""
return list(map(lambda s:list(map(lambda v:func(*v), zip(*s))), zip(*msgs)))
def msgget(msg, key, default=None):
return mapmsgs(lambda d:d.get(key, default), msg)
## conversion helpers
def str2bytes(s):
return bytes(s, 'utf8')
def bytes2str(b):
return str(b, 'utf8', 'replace')
def bytes2base64(b):
return str(codecs.encode(b, 'base64').strip(), 'ascii')
def base642bytes(b64):
if b64 is None: return None # Might be missing
return codecs.decode(bytes(b64, 'ascii'), 'base64')
## "crypto" functions
def makepad(b):
"""Returns a random pad with the same length as b,
encoded to base64.
You think this random function is weak? *Celebrate* that ;)"""
return random._urandom(len(b))
def xor2(c1, c2):
return c1^c2
def integrate2(a, b):
if a is None or a==b: return b
if b is None: return a
return "" # Invoke a conflict [0 is always wrong length ;)]
def integrate(*args):
if not args: return None
if len(args)==1: return args[0]
return integrate2(args[0], integrate(*args[1:]))
def _redact(s):
plaintext = str2bytes(s)
pad = makepad(plaintext)
return {"cipher":bytes2base64(bytes(map(xor2, plaintext, pad))), "pad":bytes2base64(pad)}
def _unredact(cipher, pad):
if pad is None: # pad is missing
return(CHAR_REDACTED*len(cipher))
if len(cipher)!=len(pad): # Failed the only integrity test ;)
return(CHAR_CONFLICT*len(cipher))
return str(bytes(map(xor2, cipher, pad)), 'utf8', 'replace')
def _disintegrate(value, k=NTRUSTEES):
lucky = random.randint(0, k-1)
return [i==lucky and value or None for i in range(k)]
def disintegrate(msg, k=NTRUSTEES):
zipped = mapmsgs(lambda p:_disintegrate(p, k), msg)
return [mapmsgs(lambda v:v[i], zipped) for i in range(k)]
def redact(s, sender, recipients=[], subject="(untitled)", k=NTRUSTEES):
msgid = makeid()
trustees = choosetrustees(k,exclude=[sender]+recipients)
header = {
"msgid": msgid, "sender": sender, "recipients": recipients,
"subject": subject, "trustees": trustees}
redaction = mapmsgs(_redact, str2msg(s))
pads = dict(zip(trustees,disintegrate(msgget(redaction, 'pad'), len(trustees))))
result = {}
result['__public__'] = header.copy()
result['__public__']['cipher'] = msgget(redaction, 'cipher')
result['__to__'] = header.copy()
result['__to__']['pads'] = pads # the whole shebang
for t in trustees:
result[t] = header.copy()
result[t]['pads'] = {t: pads[t]}
return result;
def unredact(cipher, pads, trustees=[]):
cipher64 = cipher['cipher']
pads64 = [pads['pads'][t] for t in pads['pads']
if not trustees or t in trustees]
return msg2str(mapmsgs(
_unredact,
mapmsgs(base642bytes, cipher64),
mapmsgs(base642bytes,
mapmsgs(integrate, *pads64))))
## Game functions
def makeid():
"""msgid (e.g. can be used as hashtags in move twists).
Sorting by it is like sorting by time,
it looks fancy enough, and would probably never collide"""
return 'DA{:.4f}'.format(time.time()).replace('.', '')
def getplayers():
"returns players.csv as a dict"
c=csv.reader(open('players.csv'))
names = next(c)
d = {}
for r in c:
p = dict(zip(names, r))
d[p['player']] = p
return d
def choosetrustees(k=NTRUSTEES, exclude=[], players=None):
if players is None:
players = getplayers()
candidates = list(set(players.keys())-set(exclude))
return random.sample(candidates, k)
#------ begin prefab redaction
prefab = {
"__public__": {"trustees": ["ted", "dan"], "sender": "alice",
"msgid": "DA14341968415407", "recipients": ["bob", "carol"],
"subject": "Hey, sport. YOU connect the dots.", "cipher":
[["lgPNUJU8", "qLvBBQ==", "5GfdMat4", "z8hPe4BlxxdUMXzi4w==",
"2IheMA==", "uZyB", "wVCtnQ==", "VnU=", "/pTNipiB55M=", "XcT1gQ==",
"gxU8", "9MYX", "io0="], ["0fOyC2/y", "bg8=", "bu2T", "rJH6dWUzpg==",
"B4BTkvtl", "+Ug=", "6wky", "H78=", "nx8=", "TO3/fbE=", "tqw=",
"S4k8", "+mXOsQ==", "E3rblIM=", "bIu6qbrh1g=="], ["sfjl",
"JBxnLbfe0k0n", "BShgq+QrmdquMA==", "VYD1hx8=", "MLk=", "ig==",
"oasqOoWF", "Asw=", "Rof1s/gpQQONcnk=", "ZW3zOSnTvyg=", "QlA=",
"6Q0qOw=="], ["9fPa", "2mFZdDltLL4=", "13vdBg==", "+eWH",
"koB24LLGnxs=", "CedkJ3k=", "b41+QUU=", "fRkV", "4/Yc", "anY6cA==",
"muk=", "9LrH", "5t1uXU0=", "M1eqTaWs"], ["8Ah2Pq4=", "ouQ=",
"miJQVgwle6yOcg==", "PPc=", "6jes", "kYsY2ynZdg=="]]}, "dan":
{"trustees": ["ted", "dan"], "sender": "alice", "msgid":
"DA14341968415407", "pads": {"dan": [[None, None, None, None,
"nvo/RQ==", "7ebk", None, "Pxs=", "jvG/+ffvzr8=", "KayU9Q==", "8H1Z",
"nKdz", "5OI="], [None, "GmA=", "GoX2", "/OSQHAtXxw==", "d+Ej94lL",
None, None, "fsw=", None, None, None, None, None, None,
"AO7bwt+F+A=="], [None, "YXBKZdaztixJ", "YEYD2Z1b7bPBXg==",
"NvKU5HQ=", None, "6w==", "085ZT+nx", "bao=", None, "FwiAXEih3EA=",
"ICk=", "r1l+FQ=="], [None, None, None, "jY3i", None, None,
"R/kWIDE=", "O01B", None, None, "84c=", None, "kq8PNCM=", "UCXLPs2F"],
["h2cYGdo=", "wIE=", None, None, "nl/J", None]]}, "recipients":
["bob", "carol"], "subject": "Hey, sport. YOU connect the dots."},
"__to__": {"trustees": ["ted", "dan"], "sender": "alice", "msgid":
"DA14341968415407", "pads": {"dan": [[None, None, None, None,
"nvo/RQ==", "7ebk", None, "Pxs=", "jvG/+ffvzr8=", "KayU9Q==", "8H1Z",
"nKdz", "5OI="], [None, "GmA=", "GoX2", "/OSQHAtXxw==", "d+Ej94lL",
None, None, "fsw=", None, None, None, None, None, None,
"AO7bwt+F+A=="], [None, "YXBKZdaztixJ", "YEYD2Z1b7bPBXg==",
"NvKU5HQ=", None, "6w==", "085ZT+nx", "bao=", None, "FwiAXEih3EA=",
"ICk=", "r1l+FQ=="], [None, None, None, "jY3i", None, None,
"R/kWIDE=", "O01B", None, None, "84c=", None, "kq8PNCM=", "UCXLPs2F"],
["h2cYGdo=", "wIE=", None, None, "nl/J", None]], "ted": [["xWusIv5F",
"29q4dg==", "zAa7Rc4K", "pqY7HvIXqHA1RRWMhA==", None, None,
"lSXD+g==", None, None, None, None, None, None], ["sJDRbhyB", None,
None, None, None, "uDs=", "jWhA", None, "6Ho=", "J4OQCp0=", "39g=",
"JuhF", "kgS41A==", "fR+t8fE=", None], ["5ZCA", None, None, None,
"Wco=", None, None, None, "L+mR1ohML2foHA0=", None, None, None],
["sbuL", "qhM8EFAOWM0=", "oxO8cg==", None, "/eYQidGv/nc=", "epMLVQA=",
None, None, "i5dv", "Dh9fFA==", None, "gNKi", "lrEPMyg=", None],
[None, None, "+UoxOmBAFcvrFg==", "Xo4=", None, "4f56t0C6WA=="]]},
"recipients": ["bob", "carol"], "subject": "Hey, sport. YOU connect"
"the dots."}, "ted": {"trustees": ["ted", "dan"], "sender": "alice",
"msgid": "DA14341968415407", "pads": {"ted": [["xWusIv5F", "29q4dg==",
"zAa7Rc4K", "pqY7HvIXqHA1RRWMhA==", None, None, "lSXD+g==", None,
None, None, None, None, None], ["sJDRbhyB", None, None, None, None,
"uDs=", "jWhA", None, "6Ho=", "J4OQCp0=", "39g=", "JuhF", "kgS41A==",
"fR+t8fE=", None], ["5ZCA", None, None, None, "Wco=", None, None,
None, "L+mR1ohML2foHA0=", None, None, None], ["sbuL", "qhM8EFAOWM0=",
"oxO8cg==", None, "/eYQidGv/nc=", "epMLVQA=", None, None, "i5dv",
"Dh9fFA==", None, "gNKi", "lrEPMyg=", None], [None, None,
"+UoxOmBAFcvrFg==", "Xo4=", None, "4f56t0C6WA=="]]}, "recipients":
["bob", "carol"], "subject": "Hey, sport. YOU connect the dots."}}
#------ end prefab redaction
def testit():
"Todo: turn this into proper unit tests, anyone?"
print('<!DOCTYPE html>\n<html lang="en"><head><title>Testing DarkenedAges library</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><pre>')
print('### Players')
for p in getplayers().values():
print('\n# {name} ({player}, @{twister}):'.format(**p))
print('\n'.join(' '+l for l in textwrap.wrap(p['bio'])))
print('\n\n### Redacting prefab plaintext (2 trustees)')
plaintext = "Here's the first line, \nfollowed by a second one"
redaction = redact(plaintext, 'alice', recipients=['bob','carol'], subject='Is this thing on?', k=2)
for line in textwrap.wrap(json.dumps(redaction)): print(line)
for t in redaction['__public__']['trustees']:
print('\n# >>> Unredaction for trustee: {}'.format(t))
print(unredact(redaction['__public__'], redaction[t]))
print('\n### >>> integration >>>')
print(unredact(redaction['__public__'], redaction['__to__']))
print('\n\n### Unredaction of prefab message (with conflicting pads)')
for t in prefab['__public__']['trustees']:
print('\n# >>> Unredaction for trustee: {}'.format(t))
print(unredact(prefab['__public__'], prefab[t]))
print('\n### >>> integration >>>')
print(unredact(prefab['__public__'], prefab['__to__']))
print('</pre></body></html>')
if __name__=='__main__':
testit()
<!DOCTYPE html>
<html lang="en"><head><title>Testing DarkenedAges library</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><pre>
### Players
# Sandy Claws (sandyclaws, @sandyclaws):
From the Magic Tuna Show (ask your kids).
# Rabbi Yoav (broyo, @thedod):
Confession sensor technician (CST) and ChurchApproved Rabbi (CAR)
[technically, a priest] in the Geneva parish. Born 2061 in Kibbutz
Tzofna [burnt down during the 2067 reforms]. Graduated Ofakim Yeshiva
(a ChurchApproved institute) 2085. Level 17 communism indicators
(mainly due to family and origin). 77% heterosexual. No other known
dominant deviatoins.
# Forth Inc. (forth, @forth):
Registered ████████.
# Daan II (daanii, @black_puppydog):
██ ███ ███████ of ██████.
### Redacting prefab plaintext (2 trustees)
{"daanii": {"pads": {"daanii": [["krEZr3A9", null, "Ew+m5z8=",
"zMwQeJk="], [null, "oDo=", "Gg==", null, "LSr3"]]}, "subject": "Is
this thing on?", "sender": "alice", "trustees": ["daanii", "broyo"],
"msgid": "DA14341999781041", "recipients": ["bob", "carol"]},
"__public__": {"cipher": [["2tRryldO", "5F+i", "dWbUlEs=",
"oKV+HbU="], ["to5RIGi8VsQ=", "wkM=", "ew==", "mIQCOq/5", "QkSS"]],
"subject": "Is this thing on?", "sender": "alice", "trustees":
["daanii", "broyo"], "msgid": "DA14341999781041", "recipients":
["bob", "carol"]}, "__to__": {"pads": {"daanii": [["krEZr3A9", null,
"Ew+m5z8=", "zMwQeJk="], [null, "oDo=", "Gg==", null, "LSr3"]],
"broyo": [[null, "kDfH", null, null], ["0OE9TAfLM6A=", null, null,
"6+FhVcGd", null]]}, "subject": "Is this thing on?", "sender":
"alice", "trustees": ["daanii", "broyo"], "msgid": "DA14341999781041",
"recipients": ["bob", "carol"]}, "broyo": {"pads": {"broyo": [[null,
"kDfH", null, null], ["0OE9TAfLM6A=", null, null, "6+FhVcGd", null]]},
"subject": "Is this thing on?", "sender": "alice", "trustees":
["daanii", "broyo"], "msgid": "DA14341999781041", "recipients":
["bob", "carol"]}}
# >>> Unredaction for trustee: daanii
Here's ███ first line,
████████ by a ██████ one
# >>> Unredaction for trustee: broyo
██████ the █████ █████
followed ██ █ second ███
### >>> integration >>>
Here's the first line,
followed by a second one
### Unredaction of prefab message (with conflicting pads)
# >>> Unredaction for trustee: ted
Sharky says (after interrogating ████ ███ Tung ██ ████████ ████ ███ ███ ██
access ██ ███ ███████ ██████ As far ██ we know, it may have never ███████
The █████████ ██████████ █████ is █ ██████ ██ independent ████████ ██ ████
DHQ predicts that ███ official story █████ ███ has died ██ the plane ██████
█████ ██ challenged by ███ public.
# >>> Unredaction for trustee: dan
██████ ████ ██████ █████████████ Frau Tze ████ in person), that she had no
██████ to the Pujinda paper. ██ ███ as ██ █████ ██ ███ ████ █████ leaked.
███ El-Hamdan encryption crack ██ a result of ███████████ research by FTT.
███ ████████ ████ the ████████ █████ (that FTT ███ ████ in ███ train crash)
won't be ██████████ ██ the ███████
### >>> integration >>>
Sharky says (after interrogating Frau Tze Tung in person), that she had no
access to the Pujinda paper. As far as we know, it may have never leaked.
The El-Hamdan encryption crack is a result of independent research by FTT.
DHQ predicts that the official story (that FTT has died in the ╳╳╳╳╳ crash)
won't be challenged by the public.
</pre></body></html>
We can make this file beautiful and searchable if this error is corrected: It looks like row 3 should actually have 1 column, instead of 4 in line 2.
We've moved to https://github.com/Knights-of-Redact/DarkenedAges/blob/master/players.csv
----------------------------------------------------------------------------------------
player,twister,name,bio
broyo,thedod,Rabbi Yoav,"Confession sensor technician (CST) and ChurchApproved Rabbi (CAR) [technically, a priest] in the Geneva parish. Born 2061 in Kibbutz Tzofna [burnt down during the 2067 reforms]. Graduated Ofakim Yeshiva (a ChurchApproved institute) 2085. Level 17 communism indicators (mainly due to family and origin). 77% heterosexual. No other known dominant deviatoins."
forth,forth,Forth Inc.,Registered ████████.
daanii,black_puppydog,Daan II,██ ███ ███████ of ██████.
sandyclaws,sandyclaws,Sandy Claws,From the Magic Tuna Show (ask your kids).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment