mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
5266 lines
162 KiB
Python
5266 lines
162 KiB
Python
from big_ol_pile_of_manim_imports import *
|
|
|
|
from hashlib import sha256
|
|
import binascii
|
|
|
|
#force_skipping
|
|
#revert_to_original_skipping_status
|
|
|
|
BITCOIN_COLOR = "#f7931a"
|
|
|
|
def get_cursive_name(name):
|
|
result = TextMobject("\\normalfont\\calligra %s"%name)
|
|
result.set_stroke(width = 0.5)
|
|
return result
|
|
|
|
def sha256_bit_string(message):
|
|
hexdigest = sha256(message.encode('utf-8')).hexdigest()
|
|
return bin(int(hexdigest, 16))[2:]
|
|
|
|
def bit_string_to_mobject(bit_string):
|
|
line = TexMobject("0"*32)
|
|
pre_result = VGroup(*[
|
|
line.copy() for row in range(8)
|
|
])
|
|
pre_result.arrange(DOWN, buff = SMALL_BUFF)
|
|
result = VGroup(*it.chain(*pre_result))
|
|
result.scale(0.7)
|
|
bit_string = (256 - len(bit_string))*"0" + bit_string
|
|
|
|
for i, (bit, part) in enumerate(zip(bit_string, result)):
|
|
if bit == "1":
|
|
one = TexMobject("1")[0]
|
|
one.replace(part, dim_to_match = 1)
|
|
result.submobjects[i] = one
|
|
|
|
return result
|
|
|
|
def sha256_tex_mob(message, n_forced_start_zeros = 0):
|
|
true_bit_string = sha256_bit_string(message)
|
|
n = n_forced_start_zeros
|
|
bit_string = "0"*n + true_bit_string[n:]
|
|
return bit_string_to_mobject(bit_string)
|
|
|
|
class EthereumLogo(SVGMobject):
|
|
CONFIG = {
|
|
"file_name" : "ethereum_logo",
|
|
"stroke_width" : 0,
|
|
"fill_opacity" : 1,
|
|
"color_chars" : "8B8B48",
|
|
"height" : 0.5,
|
|
}
|
|
def __init__(self, **kwargs):
|
|
SVGMobject.__init__(self, **kwargs)
|
|
for part, char in zip(self.submobjects, self.color_chars):
|
|
part.set_color("#" + 6*char)
|
|
|
|
class LitecoinLogo(SVGMobject):
|
|
CONFIG = {
|
|
"file_name" : "litecoin_logo",
|
|
"stroke_width" : 0,
|
|
"fill_opacity" : 1,
|
|
"fill_color" : LIGHT_GREY,
|
|
"height" : 0.5,
|
|
}
|
|
|
|
class TenDollarBill(VGroup):
|
|
CONFIG = {
|
|
"color" : GREEN,
|
|
"height" : 0.5,
|
|
"mark_paths_closed" : False,
|
|
}
|
|
def __init__(self, **kwargs):
|
|
VGroup.__init__(self, **kwargs)
|
|
rect = Rectangle(
|
|
height = 2.61,
|
|
width = 6.14,
|
|
color = self.color,
|
|
mark_paths_closed = False,
|
|
fill_color = BLACK,
|
|
fill_opacity = 1,
|
|
)
|
|
rect.set_height(self.height)
|
|
oval = Circle()
|
|
oval.stretch_to_fit_height(0.7*self.height)
|
|
oval.stretch_to_fit_width(0.4*self.height)
|
|
rect.add_subpath(oval.points)
|
|
|
|
pi = Randolph(
|
|
mode = "pondering",
|
|
color = GREEN_B
|
|
)
|
|
pi.set_width(oval.get_width())
|
|
pi.move_to(oval)
|
|
pi.shift(0.1*pi.get_height()*DOWN)
|
|
|
|
self.add(pi, rect)
|
|
for vect in UP+LEFT, DOWN+RIGHT:
|
|
ten = TexMobject("\\$10")
|
|
ten.set_height(0.25*self.height)
|
|
ten.next_to(self.get_corner(vect), -vect, SMALL_BUFF)
|
|
ten.set_color(GREEN_C)
|
|
self.add(ten)
|
|
|
|
|
|
##################
|
|
|
|
class AskQuestion(Scene):
|
|
CONFIG = {
|
|
"time_per_char" : 0.06,
|
|
}
|
|
def construct(self):
|
|
strings = [
|
|
"What", "does", "it", "mean ", "to",
|
|
"have ", "a", "Bitcoin?"
|
|
]
|
|
question = TextMobject(*strings)
|
|
question.set_color_by_tex("have", YELLOW)
|
|
self.wait()
|
|
for word, part in zip(strings, question):
|
|
n_chars = len(word.strip())
|
|
n_spaces = len(word) - n_chars
|
|
self.play(
|
|
OldLaggedStart(FadeIn, part),
|
|
run_time = self.time_per_char * len(word),
|
|
rate_func = squish_rate_func(smooth, 0, 0.5)
|
|
)
|
|
self.wait(self.time_per_char*n_spaces)
|
|
self.wait(2)
|
|
|
|
class ListOfAttributes(Scene):
|
|
def construct(self):
|
|
logo = BitcoinLogo()
|
|
|
|
digital = TextMobject("Digital")
|
|
government, bank = buildings = [
|
|
SVGMobject(
|
|
file_name = "%s_building"%word,
|
|
height = 2,
|
|
fill_color = LIGHT_GREY,
|
|
fill_opacity = 1,
|
|
stroke_width = 0,
|
|
)
|
|
for word in ("government", "bank")
|
|
]
|
|
attributes = VGroup(digital, *buildings)
|
|
attributes.arrange(RIGHT, buff = LARGE_BUFF)
|
|
for building in buildings:
|
|
building.cross = Cross(building)
|
|
building.cross.set_stroke(width = 12)
|
|
|
|
self.play(DrawBorderThenFill(logo))
|
|
self.play(
|
|
logo.to_corner, UP+LEFT,
|
|
Write(digital, run_time = 2)
|
|
)
|
|
for building in buildings:
|
|
self.play(FadeIn(building))
|
|
self.play(ShowCreation(building.cross))
|
|
self.wait()
|
|
|
|
class UnknownAuthor(Scene):
|
|
CONFIG = {
|
|
"camera_config" : {
|
|
"background_image" : "bitcoin_paper"
|
|
}
|
|
}
|
|
def construct(self):
|
|
rect = Rectangle(height = 0.4, width = 2.5)
|
|
rect.shift(2.45*UP)
|
|
question = TextMobject("Who is this?")
|
|
question.next_to(rect, RIGHT, buff = 1.5)
|
|
arrow = Arrow(question, rect, buff = SMALL_BUFF)
|
|
VGroup(question, arrow, rect).set_color(RED_D)
|
|
|
|
self.play(ShowCreation(rect))
|
|
self.play(
|
|
Write(question),
|
|
ShowCreation(arrow)
|
|
)
|
|
self.wait()
|
|
|
|
class DisectQuestion(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.hold_up_question()
|
|
self.list_topics()
|
|
self.isolate_you()
|
|
|
|
def hold_up_question(self):
|
|
question = TextMobject(
|
|
"What does it mean to", "have", "a", "Bitcoin?"
|
|
)
|
|
question.set_color_by_tex("have", YELLOW)
|
|
question.next_to(self.teacher, UP)
|
|
question.to_edge(RIGHT, buff = LARGE_BUFF)
|
|
question.save_state()
|
|
question.shift(DOWN)
|
|
question.set_fill(opacity = 0)
|
|
|
|
self.play(
|
|
self.teacher.change, "raise_right_hand",
|
|
question.restore
|
|
)
|
|
self.change_student_modes(*["pondering"]*3)
|
|
self.wait()
|
|
|
|
self.bitcoin_word = question.get_part_by_tex("Bitcoin")
|
|
|
|
def list_topics(self):
|
|
topics = TextMobject(
|
|
"Digital signatures, ",
|
|
"Proof of work, ",
|
|
"Cryptographic hash functions, \\dots"
|
|
)
|
|
topics.set_width(FRAME_WIDTH - LARGE_BUFF)
|
|
topics.to_edge(UP)
|
|
topics.set_color_by_tex("Digital", BLUE)
|
|
topics.set_color_by_tex("Proof", GREEN)
|
|
topics.set_color_by_tex("hash", YELLOW)
|
|
|
|
for word in topics:
|
|
anims = [Write(word, run_time = 1)]
|
|
self.change_student_modes(
|
|
*["confused"]*3,
|
|
added_anims = anims,
|
|
look_at_arg = word
|
|
)
|
|
|
|
def isolate_you(self):
|
|
self.pi_creatures = VGroup()
|
|
you = self.students[1]
|
|
rect = FullScreenFadeRectangle()
|
|
words = TextMobject("Invent your own")
|
|
arrow = Arrow(UP, DOWN)
|
|
arrow.next_to(you, UP)
|
|
words.next_to(arrow, UP)
|
|
|
|
self.revert_to_original_skipping_status()
|
|
self.play(FadeIn(rect), Animation(you))
|
|
self.play(
|
|
Write(words),
|
|
ShowCreation(arrow),
|
|
you.change, "erm", words
|
|
)
|
|
self.play(Blink(you))
|
|
self.wait()
|
|
|
|
class CryptocurrencyEquation(Scene):
|
|
def construct(self):
|
|
parts = TextMobject(
|
|
"Ledger",
|
|
"- Trust",
|
|
"+ Cryptography",
|
|
"= Cryptocurrency"
|
|
)
|
|
VGroup(*parts[-1][1:]).set_color(YELLOW)
|
|
parts.set_width(FRAME_WIDTH - LARGE_BUFF)
|
|
|
|
for part in parts:
|
|
self.play(FadeIn(part))
|
|
self.wait(2)
|
|
|
|
class CryptocurrencyMarketCaps(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ListRecentCurrencies(Scene):
|
|
def construct(self):
|
|
footnote = TextMobject("$^*$Listed by market cap")
|
|
footnote.scale(0.5)
|
|
footnote.to_corner(DOWN+RIGHT)
|
|
self.add(footnote)
|
|
|
|
logos = VGroup(
|
|
BitcoinLogo(),
|
|
EthereumLogo(),
|
|
ImageMobject("ripple_logo"),
|
|
LitecoinLogo(),
|
|
EthereumLogo().set_color_by_gradient(GREEN_B, GREEN_D),
|
|
)
|
|
for logo in logos:
|
|
logo.set_height(0.75)
|
|
logos.arrange(DOWN, buff = MED_LARGE_BUFF)
|
|
logos.shift(LEFT)
|
|
logos.to_edge(UP)
|
|
names = list(map(
|
|
TextMobject,
|
|
[
|
|
"Bitcoin", "Ethereum", "Ripple",
|
|
"Litecoin", "Ethereum Classic"
|
|
],
|
|
))
|
|
for logo, name in zip(logos, names):
|
|
name.next_to(logo, RIGHT)
|
|
anims = []
|
|
if isinstance(logo, SVGMobject):
|
|
anims.append(DrawBorderThenFill(logo, run_time = 1))
|
|
else:
|
|
anims.append(FadeIn(logo))
|
|
anims.append(Write(name, run_time = 2))
|
|
self.play(*anims)
|
|
dots = TexMobject("\\vdots")
|
|
dots.next_to(logos, DOWN)
|
|
self.play(OldLaggedStart(FadeIn, dots, run_time = 1))
|
|
self.wait()
|
|
|
|
class Hype(TeacherStudentsScene):
|
|
def construct(self):
|
|
self.teacher.change_mode("guilty")
|
|
phrases = list(map(TextMobject, [
|
|
"I want some!",
|
|
"I'll get rich, right?",
|
|
"Buy them all!"
|
|
]))
|
|
modes = ["hooray", "conniving", "surprised"]
|
|
for student, phrase, mode in zip(self.students, phrases, modes):
|
|
bubble = SpeechBubble()
|
|
bubble.set_fill(BLACK, 1)
|
|
bubble.add_content(phrase)
|
|
bubble.resize_to_content()
|
|
bubble.pin_to(student)
|
|
bubble.add(phrase)
|
|
self.play(
|
|
student.change_mode, mode,
|
|
FadeIn(bubble),
|
|
)
|
|
self.wait(3)
|
|
|
|
class NoCommentOnSpeculation(TeacherStudentsScene):
|
|
def construct(self):
|
|
axes = VGroup(
|
|
Line(0.25*LEFT, 4*RIGHT),
|
|
Line(0.25*DOWN, 3*UP),
|
|
)
|
|
times = np.arange(0, 4.25, 0.25)
|
|
prices = [
|
|
0.1, 0.5, 1.75, 1.5,
|
|
2.75, 2.2, 1.3, 0.8,
|
|
1.1, 1.3, 1.2, 1.4,
|
|
1.5, 1.7, 1.2, 1.3,
|
|
]
|
|
graph = VMobject()
|
|
graph.set_points_as_corners([
|
|
time*RIGHT + price*UP
|
|
for time, price in zip(times, prices)
|
|
])
|
|
graph.set_stroke(BLUE)
|
|
group = VGroup(axes, graph)
|
|
group.next_to(self.teacher, UP+LEFT)
|
|
|
|
cross = Cross(group)
|
|
|
|
mining_graphic = ImageMobject("bitcoin_mining_graphic")
|
|
mining_graphic.set_height(2)
|
|
mining_graphic.next_to(self.teacher, UP+LEFT)
|
|
mining_cross = Cross(mining_graphic)
|
|
mining_cross.set_stroke(RED, 8)
|
|
|
|
axes.save_state()
|
|
axes.shift(DOWN)
|
|
axes.fade(1)
|
|
self.play(
|
|
self.teacher.change, "sassy",
|
|
axes.restore,
|
|
)
|
|
self.play(ShowCreation(
|
|
graph, run_time = 2,
|
|
rate_func=linear
|
|
))
|
|
self.wait()
|
|
self.play(ShowCreation(cross))
|
|
group.add(cross)
|
|
self.play(
|
|
group.shift, FRAME_WIDTH*RIGHT,
|
|
self.teacher.change, "happy"
|
|
)
|
|
self.wait()
|
|
|
|
self.student_says(
|
|
"But...what are they?",
|
|
student_index = 0,
|
|
target_mode = "confused"
|
|
)
|
|
self.wait(2)
|
|
self.play(
|
|
FadeIn(mining_graphic),
|
|
RemovePiCreatureBubble(self.students[0]),
|
|
self.teacher.change, "sassy",
|
|
)
|
|
self.play(ShowCreation(mining_cross))
|
|
self.wait()
|
|
self.play(
|
|
VGroup(mining_graphic, mining_cross).shift,
|
|
FRAME_WIDTH*RIGHT
|
|
)
|
|
black_words = TextMobject("Random words\\\\Blah blah")
|
|
black_words.set_color(BLACK)
|
|
self.teacher_thinks(black_words)
|
|
self.zoom_in_on_thought_bubble()
|
|
|
|
class MiningIsALotteryCopy(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class LedgerScene(PiCreatureScene):
|
|
CONFIG = {
|
|
"ledger_width" : 6,
|
|
"ledger_height" : 7,
|
|
"denomination" : "USD",
|
|
"ledger_line_height" : 0.4,
|
|
"sign_transactions" : False,
|
|
"enumerate_lines" : False,
|
|
"line_number_color" : YELLOW,
|
|
}
|
|
def setup(self):
|
|
PiCreatureScene.setup(self)
|
|
self.remove(self.pi_creatures)
|
|
|
|
def add_ledger_and_network(self):
|
|
self.add(self.get_ledger(), self.get_network())
|
|
|
|
def get_ledger(self):
|
|
title = TextMobject("Ledger")
|
|
rect = Rectangle(
|
|
width = self.ledger_width,
|
|
height = self.ledger_height
|
|
)
|
|
title.next_to(rect.get_top(), DOWN)
|
|
h_line = Line(rect.get_left(), rect.get_right())
|
|
h_line.scale(0.8)
|
|
h_line.set_stroke(width = 2)
|
|
h_line.next_to(title, DOWN)
|
|
content = VGroup(h_line)
|
|
|
|
self.ledger = VGroup(rect, title, content)
|
|
self.ledger.content = content
|
|
self.ledger.to_corner(UP+LEFT)
|
|
return self.ledger
|
|
|
|
def add_line_to_ledger(self, string_or_mob):
|
|
if isinstance(string_or_mob, str):
|
|
mob = TextMobject(string_or_mob)
|
|
elif isinstance(string_or_mob, Mobject):
|
|
mob = string_or_mob
|
|
else:
|
|
raise Exception("Invalid input")
|
|
|
|
items = self.ledger.content
|
|
|
|
mob.set_height(self.ledger_line_height)
|
|
if self.enumerate_lines:
|
|
num = TexMobject(str(len(items)) + ".")
|
|
num.scale(0.8)
|
|
num.set_color(self.line_number_color)
|
|
num.next_to(mob, LEFT, MED_SMALL_BUFF)
|
|
mob.add_to_back(num)
|
|
mob.next_to(
|
|
items[-1], DOWN,
|
|
buff = MED_SMALL_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
if self.enumerate_lines and len(items) == 1:
|
|
mob.shift(MED_LARGE_BUFF * LEFT)
|
|
items.add(mob)
|
|
return mob
|
|
|
|
def add_payment_line_to_ledger(self, from_name, to_name, amount):
|
|
amount_str = str(amount)
|
|
if self.denomination == "USD":
|
|
amount_str = "\\$" + amount_str
|
|
else:
|
|
amount_str += " " + self.denomination
|
|
line_tex_parts = [
|
|
from_name.capitalize(),
|
|
"pays" if from_name.lower() != "you" else "pay",
|
|
to_name.capitalize(),
|
|
amount_str,
|
|
]
|
|
if self.sign_transactions:
|
|
line_tex_parts.append(self.get_signature_tex())
|
|
line = TextMobject(*line_tex_parts)
|
|
for name in from_name, to_name:
|
|
color = self.get_color_from_name(name)
|
|
line.set_color_by_tex(name.capitalize(), color)
|
|
if self.sign_transactions:
|
|
from_part = line.get_part_by_tex(from_name.capitalize())
|
|
line[-1].set_color(from_part.get_color())
|
|
|
|
amount_color = {
|
|
"USD" : GREEN,
|
|
"BTC" : YELLOW,
|
|
"LD" : YELLOW,
|
|
}.get(self.denomination, WHITE)
|
|
line.set_color_by_tex(amount_str, amount_color)
|
|
|
|
return self.add_line_to_ledger(line)
|
|
|
|
def get_color_from_name(self, name):
|
|
if hasattr(self, name.lower()):
|
|
creature = getattr(self, name.lower())
|
|
color = creature.get_color()
|
|
if np.mean(color.get_rgb()) < 0.5:
|
|
color = average_color(color, color, WHITE)
|
|
return color
|
|
return WHITE
|
|
|
|
def animate_payment_addition(self, *args, **kwargs):
|
|
line = self.add_payment_line_to_ledger(*args, **kwargs)
|
|
self.play(OldLaggedStart(
|
|
FadeIn,
|
|
VGroup(*it.chain(*line)),
|
|
run_time = 1
|
|
))
|
|
|
|
def get_network(self):
|
|
creatures = self.pi_creatures
|
|
lines = VGroup(*[
|
|
Line(
|
|
VGroup(pi1, pi1.label), VGroup(pi2, pi2.label),
|
|
buff = MED_SMALL_BUFF,
|
|
stroke_width = 2,
|
|
)
|
|
for pi1, pi2 in it.combinations(creatures, 2)
|
|
])
|
|
labels = VGroup(*[pi.label for pi in creatures])
|
|
self.network = VGroup(creatures, labels, lines)
|
|
self.network.lines = lines
|
|
return self.network
|
|
|
|
def create_pi_creatures(self):
|
|
creatures = VGroup(*[
|
|
PiCreature(color = color, height = 1).shift(2*vect)
|
|
for color, vect in zip(
|
|
[BLUE_C, MAROON_D, GREY_BROWN, BLUE_E],
|
|
[UP+LEFT, UP+RIGHT, DOWN+LEFT, DOWN+RIGHT],
|
|
)
|
|
])
|
|
creatures.to_edge(RIGHT)
|
|
names = self.get_names()
|
|
for name, creature in zip(names, creatures):
|
|
setattr(self, name, creature)
|
|
label = TextMobject(name.capitalize())
|
|
label.scale(0.75)
|
|
label.next_to(creature, DOWN, SMALL_BUFF)
|
|
creature.label = label
|
|
if (creature.get_center() - creatures.get_center())[0] > 0:
|
|
creature.flip()
|
|
creature.look_at(creatures.get_center())
|
|
|
|
return creatures
|
|
|
|
def get_names(self):
|
|
return ["alice", "bob", "charlie", "you"]
|
|
|
|
def get_signature_tex(self):
|
|
if not hasattr(self, "nonce"):
|
|
self.nonce = 0
|
|
binary = bin(hash(str(self.nonce)))[-8:]
|
|
self.nonce += 1
|
|
return binary + "\\dots"
|
|
|
|
def get_signature(self, color = BLUE_C):
|
|
result = TexMobject(self.get_signature_tex())
|
|
result.set_color(color)
|
|
return result
|
|
|
|
def add_ellipsis(self):
|
|
last_item = self.ledger.content[-1]
|
|
dots = TexMobject("\\vdots")
|
|
dots.next_to(last_item.get_left(), DOWN)
|
|
last_item.add(dots)
|
|
self.add(last_item)
|
|
|
|
class LayOutPlan(LedgerScene):
|
|
def construct(self):
|
|
self.ask_question()
|
|
self.show_ledger()
|
|
self.become_skeptical()
|
|
|
|
def ask_question(self):
|
|
btc = BitcoinLogo()
|
|
group = VGroup(btc, TexMobject("= ???"))
|
|
group.arrange(RIGHT)
|
|
|
|
self.play(
|
|
DrawBorderThenFill(btc),
|
|
Write(group[1], run_time = 2)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
group.scale, 0.7,
|
|
group.next_to, ORIGIN, RIGHT,
|
|
group.to_edge, UP
|
|
)
|
|
|
|
def show_ledger(self):
|
|
network = self.get_network()
|
|
ledger = self.get_ledger()
|
|
payments = [
|
|
("Alice", "Bob", 20),
|
|
("Bob", "Charlie", 40),
|
|
("Alice", "You", 50),
|
|
]
|
|
|
|
self.play(*list(map(FadeIn, [network, ledger])))
|
|
for payment in payments:
|
|
new_line = self.add_payment_line_to_ledger(*payment)
|
|
from_name, to_name, amount = payment
|
|
from_pi = getattr(self, from_name.lower())
|
|
to_pi = getattr(self, to_name.lower())
|
|
cash = TexMobject("\\$"*(amount/10))
|
|
cash.scale(0.5)
|
|
cash.move_to(from_pi)
|
|
cash.set_color(GREEN)
|
|
|
|
self.play(
|
|
cash.move_to, to_pi,
|
|
to_pi.change_mode, "hooray"
|
|
)
|
|
self.play(
|
|
FadeOut(cash),
|
|
Write(new_line, run_time = 1)
|
|
)
|
|
self.wait()
|
|
|
|
def become_skeptical(self):
|
|
creatures = self.pi_creatures
|
|
|
|
self.play(*[
|
|
ApplyMethod(pi.change_mode, "sassy")
|
|
for pi in creatures
|
|
])
|
|
for k in range(3):
|
|
self.play(*[
|
|
ApplyMethod(
|
|
creatures[i].look_at,
|
|
creatures[k*(i+1)%4]
|
|
)
|
|
for i in range(4)
|
|
])
|
|
self.wait(2)
|
|
|
|
class UnderlyingSystemVsUserFacing(Scene):
|
|
def construct(self):
|
|
underlying = TextMobject("Underlying \\\\ system")
|
|
underlying.shift(DOWN).to_edge(LEFT)
|
|
user_facing = TextMobject("User-facing")
|
|
user_facing.next_to(underlying, UP, LARGE_BUFF, LEFT)
|
|
|
|
protocol = TextMobject("Bitcoin protocol")
|
|
protocol.next_to(underlying, RIGHT, MED_LARGE_BUFF)
|
|
protocol.set_color(BITCOIN_COLOR)
|
|
banking = TextMobject("Banking system")
|
|
banking.next_to(protocol, RIGHT, MED_LARGE_BUFF)
|
|
banking.set_color(GREEN)
|
|
|
|
phone = SVGMobject(
|
|
file_name = "phone",
|
|
fill_color = WHITE,
|
|
fill_opacity = 1,
|
|
height = 1,
|
|
stroke_width = 0,
|
|
)
|
|
phone.next_to(protocol, UP, LARGE_BUFF)
|
|
card = SVGMobject(
|
|
file_name = "credit_card",
|
|
fill_color = LIGHT_GREY,
|
|
fill_opacity = 1,
|
|
stroke_width = 0,
|
|
height = 1
|
|
)
|
|
card.next_to(banking, UP, LARGE_BUFF)
|
|
|
|
btc = BitcoinLogo()
|
|
btc.next_to(phone, UP, MED_LARGE_BUFF)
|
|
dollar = TexMobject("\\$")
|
|
dollar.set_height(1)
|
|
dollar.set_color(GREEN)
|
|
dollar.next_to(card, UP, MED_LARGE_BUFF)
|
|
card.save_state()
|
|
card.shift(2*RIGHT)
|
|
card.set_fill(opacity = 0)
|
|
|
|
|
|
h_line = Line(underlying.get_left(), banking.get_right())
|
|
h_line.next_to(underlying, DOWN, MED_SMALL_BUFF, LEFT)
|
|
h_line2 = h_line.copy()
|
|
h_line2.next_to(user_facing, DOWN, MED_LARGE_BUFF, LEFT)
|
|
h_line3 = h_line.copy()
|
|
h_line3.next_to(user_facing, UP, MED_LARGE_BUFF, LEFT)
|
|
v_line = Line(5*UP, ORIGIN)
|
|
v_line.next_to(underlying, RIGHT, MED_SMALL_BUFF)
|
|
v_line.shift(1.7*UP)
|
|
v_line2 = v_line.copy()
|
|
v_line2.next_to(protocol, RIGHT, MED_SMALL_BUFF)
|
|
v_line2.shift(1.7*UP)
|
|
|
|
self.add(h_line, h_line2, h_line3, v_line, v_line2)
|
|
self.add(underlying, user_facing, btc)
|
|
self.play(Write(protocol))
|
|
self.wait(2)
|
|
self.play(
|
|
card.restore,
|
|
Write(dollar)
|
|
)
|
|
self.play(Write(banking))
|
|
self.wait(2)
|
|
self.play(DrawBorderThenFill(phone))
|
|
self.wait(2)
|
|
|
|
class FromBankToDecentralizedSystemCopy(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class IntroduceLedgerSystem(LedgerScene):
|
|
CONFIG = {
|
|
"payments" : [
|
|
("Alice", "Bob", 20),
|
|
("Bob", "Charlie", 40),
|
|
("Charlie", "You", 30),
|
|
("You", "Alice", 10),
|
|
]
|
|
}
|
|
def construct(self):
|
|
self.add(self.get_network())
|
|
self.exchange_money()
|
|
self.add_ledger()
|
|
self.tally_it_all_up()
|
|
|
|
|
|
def exchange_money(self):
|
|
for from_name, to_name, num in self.payments:
|
|
from_pi = getattr(self, from_name.lower())
|
|
to_pi = getattr(self, to_name.lower())
|
|
cash = TexMobject("\\$"*(num/10)).set_color(GREEN)
|
|
cash.set_height(0.5)
|
|
cash.move_to(from_pi)
|
|
self.play(
|
|
cash.move_to, to_pi,
|
|
to_pi.change_mode, "hooray"
|
|
)
|
|
self.play(FadeOut(cash))
|
|
self.wait()
|
|
|
|
def add_ledger(self):
|
|
ledger = self.get_ledger()
|
|
|
|
self.play(
|
|
Write(ledger),
|
|
*[
|
|
ApplyMethod(pi.change, "pondering", ledger)
|
|
for pi in self.pi_creatures
|
|
]
|
|
)
|
|
for payment in self.payments:
|
|
self.animate_payment_addition(*payment)
|
|
self.wait(3)
|
|
|
|
def tally_it_all_up(self):
|
|
accounts = dict()
|
|
names = "alice", "bob", "charlie", "you"
|
|
for name in names:
|
|
accounts[name] = 0
|
|
for from_name, to_name, amount in self.payments:
|
|
accounts[from_name.lower()] -= amount
|
|
accounts[to_name.lower()] += amount
|
|
|
|
results = VGroup()
|
|
debtors = VGroup()
|
|
creditors = VGroup()
|
|
for name in names:
|
|
amount = accounts[name]
|
|
creature = getattr(self, name)
|
|
creature.cash = TexMobject("\\$"*abs(amount/10))
|
|
creature.cash.next_to(creature, UP+LEFT, SMALL_BUFF)
|
|
creature.cash.set_color(GREEN)
|
|
if amount < 0:
|
|
verb = "Owes"
|
|
debtors.add(creature)
|
|
else:
|
|
verb = "Gets"
|
|
creditors.add(creature)
|
|
if name == "you":
|
|
verb = verb[:-1]
|
|
result = TextMobject(
|
|
verb, "\\$%d"%abs(amount)
|
|
)
|
|
result.set_color_by_tex("Owe", RED)
|
|
result.set_color_by_tex("Get", GREEN)
|
|
result.add_background_rectangle()
|
|
result.scale(0.7)
|
|
result.next_to(creature.label, DOWN)
|
|
results.add(result)
|
|
|
|
brace = Brace(VGroup(*self.ledger.content[1:]), RIGHT)
|
|
tally_up = brace.get_text("Tally up")
|
|
tally_up.add_background_rectangle()
|
|
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeIn(tally_up)
|
|
)
|
|
self.play(
|
|
OldLaggedStart(FadeIn, results),
|
|
*[
|
|
ApplyMethod(pi.change, "happy")
|
|
for pi in creditors
|
|
] + [
|
|
ApplyMethod(pi.change, "plain")
|
|
for pi in debtors
|
|
]
|
|
)
|
|
self.wait()
|
|
debtor_cash, creditor_cash = [
|
|
VGroup(*it.chain(*[pi.cash for pi in group]))
|
|
for group in (debtors, creditors)
|
|
]
|
|
self.play(FadeIn(debtor_cash))
|
|
self.play(
|
|
debtor_cash.arrange, RIGHT, SMALL_BUFF,
|
|
debtor_cash.move_to, self.pi_creatures,
|
|
)
|
|
self.wait()
|
|
self.play(ReplacementTransform(
|
|
debtor_cash, creditor_cash
|
|
))
|
|
self.wait(2)
|
|
|
|
class InitialProtocol(Scene):
|
|
def construct(self):
|
|
self.add_title()
|
|
self.show_first_two_items()
|
|
|
|
def add_title(self):
|
|
title = TextMobject("Protocol")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT).scale(4)
|
|
h_line.next_to(title, DOWN)
|
|
self.h_line = h_line
|
|
self.title = title
|
|
self.add(title, h_line)
|
|
|
|
def show_first_two_items(self):
|
|
items = VGroup(*list(map(self.get_new_item, [
|
|
"Anyone can add lines to the Ledger",
|
|
"Settle up with real money each month"
|
|
])))
|
|
|
|
for item in items:
|
|
self.wait()
|
|
self.play(OldLaggedStart(FadeIn, item))
|
|
self.wait(2)
|
|
|
|
def get_new_item(self, item_string):
|
|
item = TextMobject("$\\cdot$ %s"%item_string)
|
|
if not hasattr(self, "items"):
|
|
self.items = VGroup(item)
|
|
self.items.next_to(self.h_line, DOWN, MED_LARGE_BUFF)
|
|
else:
|
|
item.next_to(self.items, DOWN, MED_LARGE_BUFF, LEFT)
|
|
self.items.add(item)
|
|
return item
|
|
|
|
class AddFraudulentLine(LedgerScene):
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.anyone_can_add_a_line()
|
|
self.bob_adds_lines()
|
|
self.alice_reacts()
|
|
|
|
def anyone_can_add_a_line(self):
|
|
words = TextMobject("Anyone can add a line")
|
|
words.to_corner(UP+RIGHT)
|
|
words.set_color(YELLOW)
|
|
arrow = Arrow(
|
|
words.get_left(),
|
|
self.ledger.content.get_center() + DOWN,
|
|
)
|
|
|
|
self.play(Write(words, run_time = 1))
|
|
self.play(ShowCreation(arrow))
|
|
self.wait()
|
|
self.play(
|
|
FadeOut(words),
|
|
FadeOut(arrow),
|
|
FocusOn(self.bob),
|
|
)
|
|
|
|
def bob_adds_lines(self):
|
|
line = self.add_payment_line_to_ledger("Alice", "Bob", 100)
|
|
line.save_state()
|
|
line.scale(0.001)
|
|
line.move_to(self.bob)
|
|
|
|
self.play(self.bob.change, "conniving")
|
|
self.play(line.restore)
|
|
self.wait()
|
|
|
|
def alice_reacts(self):
|
|
bubble = SpeechBubble(
|
|
height = 1.5, width = 2, direction = LEFT,
|
|
)
|
|
bubble.next_to(self.alice, UP+RIGHT, buff = 0)
|
|
bubble.write("Hey!")
|
|
self.play(
|
|
Animation(self.bob.pupils),
|
|
self.alice.change, "angry",
|
|
FadeIn(bubble),
|
|
Write(bubble.content, run_time = 1)
|
|
)
|
|
self.wait(3)
|
|
self.play(
|
|
FadeOut(bubble),
|
|
FadeOut(bubble.content),
|
|
self.alice.change_mode, "pondering"
|
|
)
|
|
|
|
class AnnounceDigitalSignatures(TeacherStudentsScene):
|
|
def construct(self):
|
|
words = TextMobject("Digital \\\\ signatures!")
|
|
words.scale(1.5)
|
|
self.teacher_says(
|
|
words,
|
|
target_mode = "hooray",
|
|
)
|
|
self.change_student_modes(*["hooray"]*3)
|
|
self.wait(2)
|
|
|
|
class IntroduceSignatures(LedgerScene):
|
|
CONFIG = {
|
|
"payments" : [
|
|
("Alice", "Bob", 100),
|
|
("Charlie", "You", 20),
|
|
("Bob", "You", 30),
|
|
],
|
|
}
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.add_transactions()
|
|
self.add_signatures()
|
|
|
|
def add_transactions(self):
|
|
transactions = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in self.payments
|
|
])
|
|
self.play(OldLaggedStart(FadeIn, transactions))
|
|
self.wait()
|
|
|
|
def add_signatures(self):
|
|
signatures = VGroup(*[
|
|
get_cursive_name(payments[0].capitalize())
|
|
for payments in self.payments
|
|
])
|
|
for signature, transaction in zip(signatures, self.ledger.content[1:]):
|
|
signature.next_to(transaction, RIGHT)
|
|
signature.set_color(transaction[0].get_color())
|
|
self.play(Write(signature, run_time = 2))
|
|
transaction.add(signature)
|
|
self.wait(2)
|
|
|
|
rect = SurroundingRectangle(self.ledger.content[1])
|
|
self.play(ShowCreation(rect))
|
|
self.play(FadeOut(rect))
|
|
self.wait()
|
|
self.play(Indicate(signatures[0]))
|
|
self.wait()
|
|
|
|
class AskHowDigitalSignaturesArePossible(TeacherStudentsScene):
|
|
def construct(self):
|
|
signature = get_cursive_name("Alice")
|
|
signature.scale(1.5)
|
|
signature.set_color(BLUE_C)
|
|
signature.to_corner(UP+LEFT)
|
|
signature_copy = signature.copy()
|
|
signature_copy.shift(3*RIGHT)
|
|
|
|
bits = TexMobject("01100001")
|
|
bits.next_to(signature, DOWN)
|
|
bits.shift_onto_screen()
|
|
bits_copy = bits.copy()
|
|
bits_copy.next_to(signature_copy, DOWN)
|
|
|
|
|
|
self.student_says(
|
|
"Couldn't you just \\\\ copy the signature?",
|
|
target_mode = "confused",
|
|
run_time = 1
|
|
)
|
|
self.change_student_modes("pondering", "confused", "erm")
|
|
self.play(Write(signature))
|
|
self.play(OldLaggedStart(FadeIn, bits, run_time = 1))
|
|
self.wait()
|
|
self.play(ReplacementTransform(
|
|
bits.copy(), bits_copy,
|
|
path_arc = np.pi/2
|
|
))
|
|
self.play(Write(signature_copy))
|
|
self.wait(3)
|
|
|
|
class DescribeDigitalSignatures(LedgerScene):
|
|
CONFIG = {
|
|
"public_color" : GREEN,
|
|
"private_color" : RED,
|
|
"signature_color" : BLUE_C,
|
|
}
|
|
def construct(self):
|
|
self.reorganize_pi_creatures()
|
|
self.generate_key_pairs()
|
|
self.keep_secret_key_secret()
|
|
self.show_handwritten_signatures()
|
|
self.show_digital_signatures()
|
|
self.show_signing_functions()
|
|
|
|
def reorganize_pi_creatures(self):
|
|
self.pi_creatures.remove(self.you)
|
|
creature_groups = VGroup(*[
|
|
VGroup(pi, pi.label).scale(1.7)
|
|
for pi in self.pi_creatures
|
|
])
|
|
creature_groups.arrange(RIGHT, buff = 2)
|
|
creature_groups.to_edge(DOWN)
|
|
self.add(creature_groups)
|
|
for pi in self.pi_creatures:
|
|
if pi.is_flipped():
|
|
pi.flip()
|
|
|
|
def generate_key_pairs(self):
|
|
title = TextMobject("Private", "key /", "Public", "key")
|
|
title.to_edge(UP)
|
|
private, public = list(map(title.get_part_by_tex, ["Private", "Public"]))
|
|
private.set_color(self.private_color)
|
|
public.set_color(self.public_color)
|
|
secret = TextMobject("Secret")
|
|
secret.move_to(private, RIGHT)
|
|
secret.set_color(self.private_color)
|
|
|
|
names = self.get_names()[:-1]
|
|
public_key_strings = [
|
|
bin(256+ord(name[0].capitalize()))[3:]
|
|
for name in names
|
|
]
|
|
private_key_strings = [
|
|
bin(hash(name))[2:10]
|
|
for name in names
|
|
]
|
|
public_keys, private_keys = [
|
|
VGroup(*[
|
|
TextMobject(key_name+":"," $%s\\dots$"%key)
|
|
for key in keys
|
|
])
|
|
for key_name, keys in [
|
|
("pk", public_key_strings),
|
|
("sk", private_key_strings)
|
|
]
|
|
]
|
|
key_pairs = [
|
|
VGroup(*pair).arrange(DOWN, aligned_edge = LEFT)
|
|
for pair in zip(public_keys, private_keys)
|
|
]
|
|
for key_pair, pi in zip(key_pairs, self.pi_creatures):
|
|
key_pair.next_to(pi, UP, MED_LARGE_BUFF)
|
|
for key in key_pair:
|
|
key.set_color_by_tex("sk", self.private_color)
|
|
key.set_color_by_tex("pk", self.public_color)
|
|
|
|
self.play(Write(title, run_time = 2))
|
|
self.play(ReplacementTransform(
|
|
VGroup(VGroup(public.copy())),
|
|
public_keys
|
|
))
|
|
self.play(ReplacementTransform(
|
|
VGroup(VGroup(private.copy())),
|
|
private_keys
|
|
))
|
|
self.wait()
|
|
self.play(private.shift, DOWN)
|
|
self.play(FadeIn(secret))
|
|
self.play(FadeOut(private))
|
|
self.wait()
|
|
|
|
title.remove(private)
|
|
title.add(secret)
|
|
self.title = title
|
|
self.private_keys = private_keys
|
|
self.public_keys = public_keys
|
|
|
|
def keep_secret_key_secret(self):
|
|
keys = self.private_keys
|
|
rects = VGroup(*list(map(SurroundingRectangle, keys)))
|
|
rects.set_color(self.private_color)
|
|
lock = SVGMobject(
|
|
file_name = "lock",
|
|
height = rects.get_height(),
|
|
fill_color = LIGHT_GREY,
|
|
fill_opacity = 1,
|
|
stroke_width = 0,
|
|
)
|
|
locks = VGroup(*[
|
|
lock.copy().next_to(rect, LEFT, SMALL_BUFF)
|
|
for rect in rects
|
|
])
|
|
|
|
self.play(ShowCreation(rects))
|
|
self.play(OldLaggedStart(DrawBorderThenFill, locks))
|
|
self.wait()
|
|
|
|
self.private_key_rects = rects
|
|
self.locks = locks
|
|
|
|
def show_handwritten_signatures(self):
|
|
lines = VGroup(*[Line(LEFT, RIGHT) for x in range(5)])
|
|
lines.arrange(DOWN)
|
|
last_line = lines[-1]
|
|
last_line.scale(0.7, about_point = last_line.get_left())
|
|
|
|
signature_line = lines[0].copy()
|
|
signature_line.set_stroke(width = 2)
|
|
signature_line.next_to(lines, DOWN, LARGE_BUFF)
|
|
ex = TexMobject("\\times")
|
|
ex.scale(0.7)
|
|
ex.next_to(signature_line, UP, SMALL_BUFF, LEFT)
|
|
lines.add(ex, signature_line)
|
|
|
|
rect = SurroundingRectangle(
|
|
lines,
|
|
color = LIGHT_GREY,
|
|
buff = MED_SMALL_BUFF
|
|
)
|
|
document = VGroup(rect, lines)
|
|
documents = VGroup(*[
|
|
document.copy()
|
|
for x in range(2)
|
|
])
|
|
documents.arrange(RIGHT, buff = MED_LARGE_BUFF)
|
|
documents.to_corner(UP+LEFT)
|
|
|
|
signatures = VGroup()
|
|
for document in documents:
|
|
signature = get_cursive_name("Alice")
|
|
signature.set_color(self.signature_color)
|
|
line = document[1][-1]
|
|
signature.next_to(line, UP, SMALL_BUFF)
|
|
signatures.add(signature)
|
|
|
|
self.play(
|
|
FadeOut(self.title),
|
|
OldLaggedStart(FadeIn, documents, run_time = 1)
|
|
)
|
|
self.play(Write(signatures))
|
|
self.wait()
|
|
|
|
self.signatures = signatures
|
|
self.documents = documents
|
|
|
|
def show_digital_signatures(self):
|
|
rect = SurroundingRectangle(VGroup(
|
|
self.public_keys[0],
|
|
self.private_key_rects[0],
|
|
self.locks[0]
|
|
))
|
|
digital_signatures = VGroup()
|
|
for i, signature in enumerate(self.signatures):
|
|
bits = bin(hash(str(i)))[-8:]
|
|
digital_signature = TexMobject(bits + "\\dots")
|
|
digital_signature.scale(0.7)
|
|
digital_signature.set_color(signature.get_color())
|
|
digital_signature.move_to(signature, DOWN)
|
|
digital_signatures.add(digital_signature)
|
|
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
rect.get_corner(UP), sig.get_bottom(),
|
|
tip_length = 0.15,
|
|
color = WHITE
|
|
)
|
|
for sig in digital_signatures
|
|
])
|
|
|
|
words = VGroup(*list(map(
|
|
TextMobject,
|
|
["Different messages", "Completely different signatures"]
|
|
)))
|
|
words.arrange(DOWN, aligned_edge = LEFT)
|
|
words.scale(1.3)
|
|
words.next_to(self.documents, RIGHT)
|
|
|
|
self.play(FadeIn(rect))
|
|
self.play(*list(map(ShowCreation, arrows)))
|
|
self.play(Transform(self.signatures, digital_signatures))
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "pondering", digital_signatures)
|
|
for pi in self.pi_creatures
|
|
])
|
|
for word in words:
|
|
self.play(FadeIn(word))
|
|
self.wait()
|
|
self.play(FadeOut(words))
|
|
|
|
def show_signing_functions(self):
|
|
sign = TextMobject(
|
|
"Sign(", "Message", ", ", "sk", ") = ", "Signature",
|
|
arg_separator = ""
|
|
)
|
|
sign.to_corner(UP+RIGHT)
|
|
verify = TextMobject(
|
|
"Verify(", "Message", ", ", "Signature", ", ", "pk", ") = ", "T/F",
|
|
arg_separator = ""
|
|
)
|
|
for mob in sign, verify:
|
|
mob.set_color_by_tex("sk", self.private_color)
|
|
mob.set_color_by_tex("pk", self.public_color)
|
|
mob.set_color_by_tex(
|
|
"Signature", self.signature_color,
|
|
)
|
|
for name in "Message", "sk", "Signature", "pk":
|
|
part = mob.get_part_by_tex(name)
|
|
if part is not None:
|
|
setattr(mob, name.lower(), part)
|
|
verify.next_to(sign, DOWN, MED_LARGE_BUFF, LEFT)
|
|
VGroup(sign, verify).to_corner(UP+RIGHT)
|
|
|
|
private_key = self.private_key_rects[0]
|
|
public_key = self.public_keys[0]
|
|
message = self.documents[0]
|
|
signature = self.signatures[0]
|
|
|
|
self.play(*[
|
|
FadeIn(part)
|
|
for part in sign
|
|
if part not in [sign.message, sign.sk, sign.signature]
|
|
])
|
|
self.play(ReplacementTransform(
|
|
message.copy(), VGroup(sign.message)
|
|
))
|
|
self.wait()
|
|
self.play(ReplacementTransform(
|
|
private_key.copy(), sign.sk
|
|
))
|
|
self.wait()
|
|
self.play(ReplacementTransform(
|
|
VGroup(sign.sk, sign.message).copy(),
|
|
VGroup(sign.signature)
|
|
))
|
|
self.wait()
|
|
self.play(Indicate(sign.sk))
|
|
self.wait()
|
|
self.play(Indicate(sign.message))
|
|
self.wait()
|
|
self.play(*[
|
|
FadeIn(part)
|
|
for part in verify
|
|
if part not in [
|
|
verify.message, verify.signature,
|
|
verify.pk, verify[-1]
|
|
]
|
|
])
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(
|
|
sign.message.copy(), verify.message
|
|
),
|
|
ReplacementTransform(
|
|
sign.signature.copy(), verify.signature
|
|
)
|
|
)
|
|
self.wait()
|
|
self.play(ReplacementTransform(
|
|
public_key.copy(), VGroup(verify.pk)
|
|
))
|
|
self.wait()
|
|
self.play(Write(verify[-1]))
|
|
self.wait()
|
|
|
|
class TryGuessingDigitalSignature(Scene):
|
|
def construct(self):
|
|
verify = TextMobject(
|
|
"Verify(", "Message", ", ",
|
|
"256 bit Signature", ", ", "pk", ")",
|
|
arg_separator = ""
|
|
)
|
|
verify.scale(1.5)
|
|
verify.shift(DOWN)
|
|
signature = verify.get_part_by_tex("Signature")
|
|
verify.set_color_by_tex("Signature", BLUE)
|
|
verify.set_color_by_tex("pk", GREEN)
|
|
brace = Brace(signature, UP)
|
|
|
|
zeros_row = TexMobject("0"*32)
|
|
zeros = VGroup(*[zeros_row.copy() for x in range(8)])
|
|
zeros.arrange(DOWN, buff = SMALL_BUFF)
|
|
zeros.next_to(brace, UP)
|
|
|
|
self.add(verify)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
FadeIn(
|
|
zeros,
|
|
lag_ratio = 0.5,
|
|
run_time = 3
|
|
)
|
|
)
|
|
self.wait()
|
|
for n in range(2**10):
|
|
last_row = zeros[-1]
|
|
binary = bin(n)[2:]
|
|
for i, bit_str in enumerate(reversed(binary)):
|
|
curr_bit = last_row.submobjects[-i-1]
|
|
new_bit = TexMobject(bit_str)
|
|
new_bit.replace(curr_bit, dim_to_match = 1)
|
|
last_row.submobjects[-i-1] = new_bit
|
|
self.remove(curr_bit)
|
|
self.add(last_row)
|
|
self.wait(1./30)
|
|
|
|
class WriteTwoTo256PossibleSignatures(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"$2^{256}$", "possible\\\\", "signatures"
|
|
)
|
|
words.scale(2)
|
|
words.set_color_by_tex("256", BLUE)
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
class SupplementVideoWrapper(Scene):
|
|
def construct(self):
|
|
title = TextMobject("How secure is 256 bit security?")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
rect = ScreenRectangle(height = 6)
|
|
rect.next_to(title, DOWN)
|
|
self.add(title)
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
|
|
class FeelConfidentWithVerification(PiCreatureScene):
|
|
def construct(self):
|
|
self.show_verification()
|
|
self.show_secret_key()
|
|
|
|
def show_verification(self):
|
|
verify = TextMobject(
|
|
"Verify(", "Message", ", ",
|
|
"256 bit Signature", ", ", "pk", ")",
|
|
arg_separator = ""
|
|
)
|
|
signature_word = verify.get_part_by_tex("Signature")
|
|
verify.set_color_by_tex("Signature", BLUE)
|
|
verify.set_color_by_tex("pk", GREEN)
|
|
brace = Brace(signature_word, UP)
|
|
signature = sha256_tex_mob("Signature")
|
|
signature.next_to(brace, UP)
|
|
signature.set_color(BLUE_C)
|
|
|
|
rhs = TextMobject("=", "True")
|
|
rhs.set_color_by_tex("True", YELLOW)
|
|
rhs.next_to(verify, RIGHT)
|
|
|
|
pk = verify.get_part_by_tex("pk")
|
|
sk = TextMobject("sk")
|
|
sk.set_color(RED)
|
|
arrow = TexMobject("\\Updownarrow")
|
|
arrow.next_to(pk, DOWN)
|
|
sk.next_to(arrow, DOWN)
|
|
sk_group = VGroup(arrow, sk)
|
|
lock_box = SurroundingRectangle(sk_group, buff = SMALL_BUFF)
|
|
lock_box.set_color(RED)
|
|
lock = SVGMobject(
|
|
file_name = "lock",
|
|
fill_color = LIGHT_GREY,
|
|
height = 0.5,
|
|
)
|
|
lock.next_to(lock_box, LEFT, SMALL_BUFF)
|
|
|
|
self.add(verify)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(signature),
|
|
self.pi_creature.change, "pondering"
|
|
)
|
|
self.play(ReplacementTransform(
|
|
verify.copy(), rhs
|
|
))
|
|
self.wait()
|
|
self.play(self.pi_creature.change, "happy")
|
|
self.play(Write(sk_group))
|
|
self.play(
|
|
ShowCreation(lock_box),
|
|
DrawBorderThenFill(lock, run_time = 1)
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
def show_secret_key(self):
|
|
pass
|
|
|
|
|
|
#####
|
|
|
|
def create_pi_creature(self):
|
|
return Randolph().to_corner(DOWN+LEFT)
|
|
|
|
class IncludeTransactionNumber(LedgerScene):
|
|
CONFIG = {
|
|
"ledger_width" : 7,
|
|
}
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.add_signed_payment()
|
|
self.fail_to_sign_new_transaction()
|
|
self.copy_payment_many_times()
|
|
self.add_ids()
|
|
|
|
def add_signed_payment(self):
|
|
line = self.add_payment_line_to_ledger(
|
|
"Alice", "Bob", 100
|
|
)
|
|
signature = self.get_signature()
|
|
signature.scale(0.7)
|
|
signature.next_to(line, RIGHT)
|
|
signature.save_state()
|
|
signature.scale(0.1)
|
|
signature.move_to(self.alice)
|
|
|
|
self.play(Write(line, run_time = 1))
|
|
self.play(
|
|
signature.restore,
|
|
self.alice.change, "raise_left_hand"
|
|
)
|
|
self.wait()
|
|
self.play(self.alice.change, "happy")
|
|
self.wait()
|
|
|
|
line.add(signature)
|
|
|
|
def fail_to_sign_new_transaction(self):
|
|
payment = self.add_payment_line_to_ledger("Alice", "Bob", 3000)
|
|
q_marks = TexMobject("???")
|
|
q_marks.next_to(payment, RIGHT)
|
|
cross = Cross(payment)
|
|
payment.save_state()
|
|
payment.move_to(self.bob.get_corner(UP+LEFT))
|
|
payment.set_fill(opacity = 0)
|
|
|
|
self.play(
|
|
self.bob.change, "raise_right_hand",
|
|
payment.restore,
|
|
)
|
|
self.play(
|
|
self.bob.change, "confused",
|
|
Write(q_marks)
|
|
)
|
|
self.play(ShowCreation(cross))
|
|
self.wait()
|
|
self.play(*list(map(FadeOut, [payment, cross, q_marks])))
|
|
self.ledger.content.remove(payment)
|
|
|
|
def copy_payment_many_times(self):
|
|
line = self.ledger.content[-1]
|
|
copies = VGroup(*[line.copy() for x in range(4)])
|
|
copies.arrange(DOWN, buff = MED_SMALL_BUFF)
|
|
copies.next_to(line, DOWN, buff = MED_SMALL_BUFF)
|
|
|
|
self.play(
|
|
OldLaggedStart(FadeIn, copies, run_time = 3),
|
|
self.bob.change, "conniving",
|
|
)
|
|
self.play(self.alice.change, "angry")
|
|
self.wait()
|
|
|
|
self.copies = copies
|
|
|
|
def add_ids(self):
|
|
top_line = self.ledger.content[-1]
|
|
lines = VGroup(top_line, *self.copies)
|
|
numbers = VGroup()
|
|
old_signatures = VGroup()
|
|
new_signatures = VGroup()
|
|
colors = list(Color(BLUE_B).range_to(GREEN_B, len(lines)))
|
|
for i, line in enumerate(lines):
|
|
number = TexMobject(str(i))
|
|
number.scale(0.7)
|
|
number.set_color(YELLOW)
|
|
number.next_to(line, LEFT)
|
|
numbers.add(number)
|
|
line.add_to_back(number)
|
|
old_signature = line[-1]
|
|
new_signature = self.get_signature()
|
|
new_signature.replace(old_signature)
|
|
new_signature.set_color(colors[i])
|
|
old_signatures.add(old_signature)
|
|
new_signatures.add(VGroup(new_signature))
|
|
line.remove(old_signature)
|
|
|
|
self.play(
|
|
Write(numbers),
|
|
self.alice.change, "thinking"
|
|
)
|
|
self.play(FadeOut(old_signatures))
|
|
self.play(ReplacementTransform(
|
|
lines.copy(), new_signatures,
|
|
lag_ratio = 0.5,
|
|
run_time = 2,
|
|
))
|
|
self.play(self.bob.change, "erm")
|
|
self.wait(2)
|
|
|
|
class ProtocolWithDigitalSignatures(InitialProtocol):
|
|
def construct(self):
|
|
self.force_skipping()
|
|
InitialProtocol.construct(self)
|
|
self.revert_to_original_skipping_status()
|
|
|
|
rect = SurroundingRectangle(self.items[-1])
|
|
rect.set_color(RED)
|
|
|
|
new_item = self.get_new_item(
|
|
"Only signed transactions are valid"
|
|
)
|
|
new_item.set_color(YELLOW)
|
|
|
|
self.play(Write(new_item))
|
|
self.wait()
|
|
self.play(ShowCreation(rect))
|
|
self.wait()
|
|
|
|
class SignedLedgerScene(LedgerScene):
|
|
CONFIG = {
|
|
"sign_transactions" : True,
|
|
"enumerate_lines" : True,
|
|
"ledger_width" : 7.5,
|
|
}
|
|
|
|
class CharlieRacksUpDebt(SignedLedgerScene):
|
|
CONFIG = {
|
|
"payments" : [
|
|
("Charlie", "Alice", 100),
|
|
("Charlie", "Bob", 200),
|
|
("Charlie", "You", 800),
|
|
("Charlie", "Bob", 600),
|
|
("Charlie", "Alice", 900),
|
|
],
|
|
}
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
lines = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in self.payments
|
|
])
|
|
|
|
self.play(OldLaggedStart(
|
|
FadeIn, lines,
|
|
run_time = 3,
|
|
lag_ratio = 0.25
|
|
))
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "sassy", self.charlie)
|
|
for pi in self.pi_creatures
|
|
if pi is not self.charlie
|
|
])
|
|
self.play(
|
|
self.charlie.shift, FRAME_X_RADIUS*RIGHT,
|
|
rate_func = running_start
|
|
)
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "angry", self.charlie)
|
|
for pi in self.get_pi_creatures()
|
|
])
|
|
self.wait()
|
|
|
|
class CharlieFeelsGuilty(Scene):
|
|
def construct(self):
|
|
charlie = PiCreature(color = GREY_BROWN)
|
|
charlie.scale(2)
|
|
|
|
self.play(FadeIn(charlie))
|
|
self.play(charlie.change, "sad")
|
|
for x in range(2):
|
|
self.play(Blink(charlie))
|
|
self.wait(2)
|
|
|
|
class ThinkAboutSettlingUp(Scene):
|
|
def construct(self):
|
|
randy = Randolph()
|
|
randy.to_corner(DOWN+LEFT)
|
|
|
|
self.play(PiCreatureBubbleIntroduction(
|
|
randy,
|
|
"You don't \\emph{actually} \\\\" + \
|
|
"need to settle up $\\dots$",
|
|
bubble_class = ThoughtBubble,
|
|
target_mode = "thinking"
|
|
))
|
|
self.play(Blink(randy))
|
|
self.wait()
|
|
|
|
class DontAllowOverdrawing(InitialProtocol):
|
|
def construct(self):
|
|
self.add_title()
|
|
lines = list(map(self.get_new_item, [
|
|
"Anyone can add lines to the Ledger \\,",
|
|
"Only signed transactions are valid \\,",
|
|
"No overspending"
|
|
]))
|
|
lines[2].set_color(YELLOW)
|
|
|
|
self.add(*lines[:2])
|
|
self.wait()
|
|
self.play(Write(lines[2]))
|
|
self.wait()
|
|
|
|
class LedgerWithInitialBuyIn(SignedLedgerScene):
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.everyone_buys_in()
|
|
self.add_initial_lines()
|
|
self.add_charlie_payments()
|
|
self.point_out_charlie_is_broke()
|
|
self.running_balance()
|
|
|
|
def everyone_buys_in(self):
|
|
center = self.network.get_center()
|
|
moneys = VGroup(*[
|
|
TexMobject("\\$100")
|
|
for pi in self.pi_creatures
|
|
])
|
|
moneys.set_color(GREEN)
|
|
for pi, money in zip(reversed(self.pi_creatures), moneys):
|
|
vect = pi.get_center() - center
|
|
money.next_to(center, vect, SMALL_BUFF)
|
|
money.add_background_rectangle()
|
|
money.save_state()
|
|
money.scale(0.01)
|
|
corner = pi.get_corner(UP + np.sign(vect[0])*LEFT)
|
|
money.move_to(corner)
|
|
|
|
self.play(
|
|
OldLaggedStart(
|
|
ApplyMethod, moneys,
|
|
lambda m : (m.restore,)
|
|
),
|
|
self.charlie.change, "raise_right_hand",
|
|
self.you.change, "raise_right_hand",
|
|
)
|
|
self.network.add(moneys)
|
|
|
|
def add_initial_lines(self):
|
|
lines = VGroup()
|
|
for name in self.get_names():
|
|
new_line = TextMobject(
|
|
name.capitalize(),
|
|
"get" if name == "you" else "gets",
|
|
"\\$100"
|
|
)
|
|
new_line.set_color_by_tex(
|
|
name.capitalize(),
|
|
self.get_color_from_name(name)
|
|
)
|
|
new_line.set_color_by_tex("100", GREEN)
|
|
self.add_line_to_ledger(new_line)
|
|
lines.add(new_line)
|
|
line = Line(LEFT, RIGHT)
|
|
line.set_width(self.ledger.get_width())
|
|
line.scale_in_place(0.9)
|
|
line.next_to(lines[-1], DOWN, SMALL_BUFF, LEFT)
|
|
line.set_stroke(width = 1)
|
|
lines[-1].add(line)
|
|
|
|
self.play(
|
|
OldLaggedStart(FadeIn, lines),
|
|
*[
|
|
ApplyMethod(pi.change, "thinking", self.ledger)
|
|
for pi in self.pi_creatures
|
|
]
|
|
)
|
|
self.wait()
|
|
|
|
def add_charlie_payments(self):
|
|
payments = [
|
|
("Charlie", "Alice", 50),
|
|
("Charlie", "Bob", 50),
|
|
("Charlie", "You", 20),
|
|
]
|
|
new_lines = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in payments
|
|
])
|
|
|
|
for line in new_lines:
|
|
self.play(Write(line, run_time = 1))
|
|
self.wait()
|
|
|
|
def point_out_charlie_is_broke(self):
|
|
charlie_lines = VGroup(*[
|
|
VGroup(*self.ledger.content[i][1:5])
|
|
for i in (3, 5, 6, 7)
|
|
])
|
|
rects = VGroup(*[
|
|
SurroundingRectangle(line)
|
|
for line in charlie_lines
|
|
])
|
|
rects.set_color(YELLOW)
|
|
rects.set_stroke(width = 2)
|
|
last_rect = rects[-1]
|
|
last_rect.set_stroke(RED, 4)
|
|
rects.remove(last_rect)
|
|
invalid = TextMobject("Invalid")
|
|
invalid.set_color(RED)
|
|
invalid.next_to(last_rect, DOWN)
|
|
|
|
self.play(ShowCreation(rects))
|
|
self.play(self.charlie.change_mode, "guilty")
|
|
self.wait()
|
|
self.play(ShowCreation(last_rect))
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "sassy", self.charlie)
|
|
for pi in self.pi_creatures
|
|
if pi is not self.charlie
|
|
])
|
|
self.play(Write(invalid))
|
|
self.wait(2)
|
|
self.play(*list(map(FadeOut, [rects, last_rect, invalid])))
|
|
|
|
def running_balance(self):
|
|
charlie_lines = VGroup(*[
|
|
VGroup(*self.ledger.content[i][1:5])
|
|
for i in (3, 5, 6, 7)
|
|
])
|
|
signatures = VGroup(*[
|
|
self.ledger.content[i][5]
|
|
for i in (5, 6, 7)
|
|
])
|
|
rect = Rectangle(color = WHITE)
|
|
rect.set_fill(BLACK, 0.8)
|
|
rect.stretch_to_fit_height(self.ledger.get_height() - 2*MED_SMALL_BUFF)
|
|
title = TextMobject("Charlie's running \\\\ balance")
|
|
rect.stretch_to_fit_width(title.get_width() + 2*MED_SMALL_BUFF)
|
|
rect.move_to(self.ledger.get_right())
|
|
title.next_to(rect.get_top(), DOWN)
|
|
balance = VGroup(rect, title)
|
|
|
|
lines = VGroup(*list(map(TextMobject, [
|
|
"\\$100", "\\$50", "\\$0", "Overdrawn"
|
|
])))
|
|
lines.set_color(GREEN)
|
|
lines[-1].set_color(RED)
|
|
arrows = VGroup()
|
|
for line, c_line in zip(lines, charlie_lines):
|
|
line.next_to(rect.get_left(), RIGHT, LARGE_BUFF)
|
|
line.shift(
|
|
(c_line.get_center() - line.get_center())[1]*UP
|
|
)
|
|
arrow = Arrow(c_line, line)
|
|
arrows.add(arrow)
|
|
|
|
self.pi_creatures.remove(self.alice, self.charlie)
|
|
self.play(
|
|
FadeOut(signatures),
|
|
FadeIn(balance)
|
|
)
|
|
self.play(
|
|
OldLaggedStart(FadeIn, lines, run_time = 3),
|
|
OldLaggedStart(ShowCreation, arrows, run_time = 3),
|
|
)
|
|
self.wait()
|
|
|
|
class RemovedConnectionBetweenLedgerAndCash(TeacherStudentsScene):
|
|
def construct(self):
|
|
ledger = Rectangle(
|
|
height = 2, width = 1.5,
|
|
color = WHITE
|
|
)
|
|
ledger_name = TextMobject("Ledger")
|
|
ledger_name.set_width(ledger.get_width() - MED_SMALL_BUFF)
|
|
ledger_name.next_to(ledger.get_top(), DOWN)
|
|
ledger.add(ledger_name)
|
|
|
|
arrow = TexMobject("\\leftrightarrow")
|
|
cash = TexMobject("\\$\\$\\$")
|
|
cash.set_color(GREEN)
|
|
arrow.next_to(ledger, RIGHT)
|
|
cash.next_to(arrow, RIGHT)
|
|
group = VGroup(ledger, arrow, cash)
|
|
group.next_to(self.teacher, UP+LEFT)
|
|
|
|
self.add(group)
|
|
self.play(
|
|
Animation(group),
|
|
self.teacher.change, "raise_right_hand"
|
|
)
|
|
self.play(
|
|
arrow.shift, 2*UP,
|
|
arrow.set_fill, None, 0
|
|
)
|
|
self.play(
|
|
ledger.shift, LEFT,
|
|
cash.shift, RIGHT
|
|
)
|
|
self.change_student_modes(
|
|
*["pondering"]*3,
|
|
look_at_arg = ledger,
|
|
added_anims = [self.teacher.change, "happy"]
|
|
)
|
|
self.wait(3)
|
|
|
|
class RenameToLedgerDollars(LedgerScene):
|
|
CONFIG = {
|
|
"payments" : [
|
|
("Alice", "Bob", 20),
|
|
("Charlie", "You", 80),
|
|
("Bob", "Charlie", 60),
|
|
("Bob", "Alice", 30),
|
|
("Alice", "You", 100),
|
|
],
|
|
"enumerate_lines" : True,
|
|
"line_number_color" : WHITE,
|
|
}
|
|
def construct(self):
|
|
self.add(self.get_ledger())
|
|
self.add_bubble()
|
|
self.jump_in_to_middle()
|
|
self.add_payments_in_dollars()
|
|
self.rewrite_as_ledger_dollars()
|
|
|
|
def add_bubble(self):
|
|
randy = self.pi_creature
|
|
bubble = SpeechBubble(direction = RIGHT)
|
|
bubble.write("Who needs \\\\ cash?")
|
|
bubble.resize_to_content()
|
|
bubble.add(bubble.content)
|
|
bubble.pin_to(randy)
|
|
bubble.shift(MED_LARGE_BUFF*RIGHT)
|
|
self.bubble = bubble
|
|
|
|
self.add(randy, bubble)
|
|
self.add_foreground_mobject(bubble)
|
|
|
|
def jump_in_to_middle(self):
|
|
h_line = self.ledger.content[0]
|
|
dots = TexMobject("\\vdots")
|
|
dots.next_to(h_line.get_left(), DOWN)
|
|
h_line.add(dots)
|
|
self.add(h_line)
|
|
point = VectorizedPoint(h_line.get_corner(DOWN+LEFT))
|
|
point.shift(MED_SMALL_BUFF*LEFT)
|
|
self.ledger.content.add(*[
|
|
point.copy()
|
|
for x in range(103)
|
|
])
|
|
|
|
def add_payments_in_dollars(self):
|
|
lines = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in self.payments
|
|
])
|
|
|
|
self.play(OldLaggedStart(
|
|
FadeIn, lines,
|
|
run_time = 4,
|
|
lag_ratio = 0.3
|
|
))
|
|
self.wait()
|
|
|
|
self.payment_lines = lines
|
|
|
|
def rewrite_as_ledger_dollars(self):
|
|
curr_lines = self.payment_lines
|
|
amounts = VGroup(*[line[4] for line in curr_lines])
|
|
amounts.target = VGroup()
|
|
for amount in amounts:
|
|
dollar_sign = amount[0]
|
|
amount.remove(dollar_sign)
|
|
amount.add(dollar_sign)
|
|
tex_string = amount.get_tex_string()
|
|
ld = TextMobject(tex_string[2:] + " LD")
|
|
ld.set_color(YELLOW)
|
|
ld.scale(0.8)
|
|
ld.move_to(amount, LEFT)
|
|
amounts.target.add(ld)
|
|
|
|
ledger_dollars = TextMobject("Ledger Dollars \\\\ ``LD'' ")
|
|
ledger_dollars.set_color(YELLOW)
|
|
ledger_dollars.next_to(self.ledger, RIGHT)
|
|
|
|
self.play(
|
|
Write(ledger_dollars),
|
|
FadeOut(self.bubble),
|
|
self.pi_creature.change, "thinking", ledger_dollars
|
|
)
|
|
self.play(MoveToTarget(amounts))
|
|
self.wait(2)
|
|
|
|
|
|
###
|
|
|
|
def create_pi_creatures(self):
|
|
LedgerScene.create_pi_creatures(self)
|
|
randy = Randolph(mode = "shruggie").flip()
|
|
randy.to_corner(DOWN+RIGHT)
|
|
return VGroup(randy)
|
|
|
|
class ExchangeCashForLedgerDollars(LedgerScene):
|
|
CONFIG = {
|
|
"sign_transactions" : True,
|
|
"denomination" : "LD",
|
|
"ledger_width" : 7.5,
|
|
"ledger_height" : 6,
|
|
}
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.add_ellipsis()
|
|
self.add_title()
|
|
self.give_ten_dollar_bill()
|
|
self.add_bob_pays_alice_line()
|
|
self.everyone_thinks()
|
|
|
|
def add_title(self):
|
|
self.ledger.shift(DOWN)
|
|
title = TextMobject(
|
|
"Exchange", "LD", "for", "\\$\\$\\$"
|
|
)
|
|
title.set_color_by_tex("LD", YELLOW)
|
|
title.set_color_by_tex("\\$", GREEN)
|
|
title.scale(1.3)
|
|
title.to_edge(UP).shift(LEFT)
|
|
|
|
self.play(Write(title))
|
|
self.wait()
|
|
|
|
def give_ten_dollar_bill(self):
|
|
bill = TenDollarBill()
|
|
bill.next_to(self.alice.get_corner(UP+RIGHT), UP)
|
|
bill.generate_target()
|
|
bill.target.next_to(self.bob.get_corner(UP+LEFT), UP)
|
|
|
|
arrow = Arrow(bill, bill.target, color = GREEN)
|
|
|
|
small_bill = bill.copy()
|
|
small_bill.scale(0.01, about_point = bill.get_bottom())
|
|
|
|
self.play(
|
|
ReplacementTransform(small_bill, bill),
|
|
self.alice.change, "raise_right_hand"
|
|
)
|
|
self.play(ShowCreation(arrow))
|
|
self.play(MoveToTarget(bill))
|
|
self.play(self.bob.change, "happy", bill)
|
|
self.wait()
|
|
|
|
def add_bob_pays_alice_line(self):
|
|
line = self.add_payment_line_to_ledger(
|
|
"Bob", "Alice", 10
|
|
)
|
|
line.save_state()
|
|
line.scale(0.01)
|
|
line.move_to(self.bob.get_corner(UP+LEFT))
|
|
self.play(self.bob.change, "raise_right_hand", line)
|
|
self.play(line.restore, run_time = 2)
|
|
self.wait()
|
|
|
|
def everyone_thinks(self):
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "thinking", self.ledger)
|
|
for pi in self.pi_creatures
|
|
])
|
|
self.wait(4)
|
|
|
|
class BitcoinIsALedger(Scene):
|
|
def construct(self):
|
|
self.add_btc_to_ledger()
|
|
self.add_currency_to_tx_history()
|
|
|
|
def add_btc_to_ledger(self):
|
|
logo = BitcoinLogo()
|
|
ledger = self.get_ledger()
|
|
arrow = TexMobject("\\Leftrightarrow")
|
|
group = VGroup(logo, arrow, ledger)
|
|
group.arrange(RIGHT)
|
|
|
|
self.play(DrawBorderThenFill(logo))
|
|
self.wait()
|
|
self.play(
|
|
Write(arrow),
|
|
Write(ledger)
|
|
)
|
|
self.wait()
|
|
|
|
self.btc_to_ledger = group
|
|
|
|
def add_currency_to_tx_history(self):
|
|
equation = TextMobject(
|
|
"Currency", "=", "Transaction history"
|
|
)
|
|
equation.set_color_by_tex("Currency", BITCOIN_COLOR)
|
|
equation.shift(
|
|
self.btc_to_ledger[1].get_center() - \
|
|
equation[1].get_center() + 2*UP
|
|
)
|
|
|
|
for part in reversed(equation):
|
|
self.play(FadeIn(part))
|
|
self.wait()
|
|
|
|
def get_ledger(self):
|
|
rect = Rectangle(height = 2, width = 1.5)
|
|
title = TextMobject("Ledger")
|
|
title.set_width(0.8*rect.get_width())
|
|
title.next_to(rect.get_top(), DOWN, SMALL_BUFF)
|
|
|
|
lines = VGroup(*[
|
|
Line(LEFT, RIGHT)
|
|
for x in range(8)
|
|
])
|
|
lines.arrange(DOWN, buff = SMALL_BUFF)
|
|
lines.stretch_to_fit_width(title.get_width())
|
|
lines.next_to(title, DOWN)
|
|
return VGroup(rect, title, lines)
|
|
|
|
class BigDifferenceBetweenLDAndCryptocurrencies(Scene):
|
|
def construct(self):
|
|
ld = TextMobject("LD").scale(1.5).set_color(YELLOW)
|
|
btc = BitcoinLogo()
|
|
eth = EthereumLogo()
|
|
ltc = LitecoinLogo()
|
|
logos = VGroup(ltc, eth, ld, btc)
|
|
cryptos = VGroup(btc, eth, ltc)
|
|
for logo in cryptos:
|
|
logo.set_height(1)
|
|
vects = compass_directions(4, DOWN+LEFT)
|
|
for logo, vect in zip(logos, vects):
|
|
logo.move_to(0.75*vect)
|
|
|
|
centralized = TextMobject("Centralized")
|
|
decentralized = TextMobject("Decentralized")
|
|
words = VGroup(centralized, decentralized)
|
|
words.scale(1.5)
|
|
words.to_edge(UP)
|
|
for word, vect in zip(words, [RIGHT, LEFT]):
|
|
word.shift(FRAME_X_RADIUS*vect/2)
|
|
|
|
self.add(logos)
|
|
self.wait()
|
|
self.play(
|
|
cryptos.next_to, decentralized, DOWN, LARGE_BUFF,
|
|
ld.next_to, centralized, DOWN, LARGE_BUFF,
|
|
)
|
|
self.play(*list(map(Write, words)))
|
|
self.wait(2)
|
|
|
|
class DistributedLedgerScene(LedgerScene):
|
|
def get_large_network(self):
|
|
network = self.get_network()
|
|
network.set_height(FRAME_HEIGHT - LARGE_BUFF)
|
|
network.center()
|
|
for pi in self.pi_creatures:
|
|
pi.label.scale(0.8, about_point = pi.get_bottom())
|
|
return network
|
|
|
|
def get_distributed_ledgers(self):
|
|
ledger = self.get_ledger()
|
|
title = ledger[1]
|
|
h_line = ledger.content
|
|
title.set_width(0.7*ledger.get_width())
|
|
title.next_to(ledger.get_top(), DOWN, MED_LARGE_BUFF)
|
|
h_line.next_to(title, DOWN)
|
|
added_lines = VGroup(*[h_line.copy() for x in range(5)])
|
|
added_lines.arrange(DOWN, buff = MED_LARGE_BUFF)
|
|
added_lines.next_to(h_line, DOWN, MED_LARGE_BUFF)
|
|
ledger.content.add(added_lines)
|
|
|
|
ledgers = VGroup()
|
|
for pi in self.pi_creatures:
|
|
pi.ledger = ledger.copy()
|
|
pi.ledger.set_height(pi.get_height())
|
|
pi.ledger[0].set_color(pi.get_color())
|
|
vect = pi.get_center()-self.pi_creatures.get_center()
|
|
x_vect = vect[0]*RIGHT
|
|
pi.ledger.next_to(pi, x_vect, SMALL_BUFF)
|
|
ledgers.add(pi.ledger)
|
|
return ledgers
|
|
|
|
def add_large_network_and_distributed_ledger(self):
|
|
self.add(self.get_large_network())
|
|
self.add(self.get_distributed_ledgers())
|
|
|
|
class TransitionToDistributedLedger(DistributedLedgerScene):
|
|
CONFIG = {
|
|
"sign_transactions" : True,
|
|
"ledger_width" : 7.5,
|
|
"ledger_height" : 6,
|
|
"enumerate_lines" : True,
|
|
"denomination" : "LD",
|
|
"line_number_color" : WHITE,
|
|
"ledger_line_height" : 0.35,
|
|
}
|
|
def construct(self):
|
|
self.add_ledger_and_network()
|
|
self.ledger.shift(DOWN)
|
|
self.add_ellipsis()
|
|
|
|
self.ask_where_is_ledger()
|
|
self.add_various_payements()
|
|
self.ask_who_controls_ledger()
|
|
self.distribute_ledger()
|
|
self.broadcast_transaction()
|
|
self.ask_about_ledger_consistency()
|
|
|
|
def ask_where_is_ledger(self):
|
|
question = TextMobject("Where", "is", "this?!")
|
|
question.set_color(RED)
|
|
question.scale(1.5)
|
|
question.next_to(self.ledger, UP)
|
|
|
|
self.play(Write(question))
|
|
self.wait()
|
|
|
|
self.question = question
|
|
|
|
def add_various_payements(self):
|
|
payments = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in [
|
|
("Alice", "Bob", 20),
|
|
("Charlie", "You", 100),
|
|
("You", "Alice", 50),
|
|
("Bob", "You", 30),
|
|
]
|
|
])
|
|
|
|
for payment in payments:
|
|
self.play(OldLaggedStart(FadeIn, payment, run_time = 1))
|
|
self.wait()
|
|
|
|
def ask_who_controls_ledger(self):
|
|
new_question = TextMobject("Who", "controls", "this?!")
|
|
new_question.scale(1.3)
|
|
new_question.move_to(self.question)
|
|
new_question.set_color(RED)
|
|
|
|
self.play(Transform(self.question, new_question))
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "confused", new_question)
|
|
for pi in self.pi_creatures
|
|
])
|
|
self.wait(2)
|
|
|
|
def distribute_ledger(self):
|
|
ledger = self.ledger
|
|
self.ledger_width = 6
|
|
self.ledger_height = 7
|
|
distribute_ledgers = self.get_distributed_ledgers()
|
|
group = VGroup(self.network, distribute_ledgers)
|
|
|
|
self.play(FadeOut(self.question))
|
|
self.play(ReplacementTransform(
|
|
VGroup(ledger), distribute_ledgers
|
|
))
|
|
self.play(*[
|
|
ApplyMethod(pi.change, "pondering", pi.ledger)
|
|
for pi in self.pi_creatures
|
|
])
|
|
self.play(
|
|
group.set_height, FRAME_HEIGHT - 2,
|
|
group.center
|
|
)
|
|
self.wait(2)
|
|
|
|
def broadcast_transaction(self):
|
|
payment = TextMobject(
|
|
"Alice", "pays", "Bob", "100 LD"
|
|
)
|
|
payment.set_color_by_tex("Alice", self.alice.get_color())
|
|
payment.set_color_by_tex("Bob", self.bob.get_color())
|
|
payment.set_color_by_tex("LD", YELLOW)
|
|
payment.scale(0.75)
|
|
payment.add_background_rectangle()
|
|
payment_copies = VGroup(*[
|
|
payment.copy().next_to(pi, UP)
|
|
for pi in self.pi_creatures
|
|
])
|
|
payment = payment_copies[0]
|
|
|
|
self.play(
|
|
self.alice.change, "raise_right_hand", payment,
|
|
Write(payment, run_time = 2)
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(
|
|
VGroup(payment), payment_copies,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.5, 1)
|
|
),
|
|
Broadcast(self.alice.get_corner(UP+RIGHT)),
|
|
self.alice.change, "happy"
|
|
)
|
|
self.wait()
|
|
pairs = list(zip(payment_copies, self.pi_creatures))
|
|
Scene.play(self, *it.chain(*[
|
|
[
|
|
pi.look_at, pi.ledger[-1],
|
|
line.scale, 0.2,
|
|
line.next_to, pi.ledger[-1], DOWN, SMALL_BUFF,
|
|
]
|
|
for line, pi in pairs
|
|
]))
|
|
self.wait(3)
|
|
|
|
for line, pi in pairs:
|
|
pi.ledger.add(line)
|
|
|
|
def ask_about_ledger_consistency(self):
|
|
ledgers = VGroup(*[
|
|
pi.ledger
|
|
for pi in self.pi_creatures
|
|
])
|
|
|
|
self.play(
|
|
FadeOut(self.network),
|
|
ledgers.scale, 2,
|
|
ledgers.arrange, RIGHT,
|
|
ledgers.space_out_submobjects,
|
|
)
|
|
|
|
question = TextMobject("Are these the same?")
|
|
question.scale(1.5)
|
|
question.to_edge(UP)
|
|
arrows = VGroup(*[
|
|
Arrow(question.get_bottom(), ledger.get_top())
|
|
for ledger in ledgers
|
|
])
|
|
|
|
self.play(*list(map(ShowCreation, arrows)))
|
|
self.play(Write(question))
|
|
self.wait()
|
|
|
|
class BobDoubtsBroadcastTransaction(DistributedLedgerScene):
|
|
def construct(self):
|
|
self.setup_bob_and_charlie()
|
|
self.bob_receives_transaction()
|
|
self.bob_tries_to_pay_charlie()
|
|
|
|
def setup_bob_and_charlie(self):
|
|
bob, charlie = self.bob, self.charlie
|
|
self.pi_creatures = VGroup(bob, charlie)
|
|
for pi in self.pi_creatures:
|
|
pi.flip()
|
|
self.pi_creatures.scale(2)
|
|
self.pi_creatures.arrange(RIGHT, buff = 5)
|
|
|
|
for name in "bob", "charlie":
|
|
label = TextMobject(name.capitalize())
|
|
pi = getattr(self, name)
|
|
label.next_to(pi, DOWN)
|
|
pi.label = label
|
|
bob.make_eye_contact(charlie)
|
|
|
|
self.get_distributed_ledgers()
|
|
|
|
self.add(bob, bob.label, bob.ledger)
|
|
|
|
def bob_receives_transaction(self):
|
|
bob, charlie = self.bob, self.charlie
|
|
corner = FRAME_Y_RADIUS*UP + FRAME_X_RADIUS*LEFT
|
|
|
|
payment = TextMobject(
|
|
"Alice", "pays", "Bob", "10 LD"
|
|
)
|
|
payment.set_color_by_tex("Alice", self.alice.get_color())
|
|
payment.set_color_by_tex("Bob", self.bob.get_color())
|
|
payment.set_color_by_tex("LD", YELLOW)
|
|
payment.next_to(corner, UP+LEFT)
|
|
|
|
self.play(
|
|
Broadcast(corner),
|
|
ApplyMethod(
|
|
payment.next_to, bob, UP,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.3, 1)
|
|
),
|
|
)
|
|
self.play(bob.look_at, payment)
|
|
self.play(
|
|
payment.scale, 0.3,
|
|
payment.next_to, bob.ledger[-1], DOWN, SMALL_BUFF
|
|
)
|
|
|
|
def bob_tries_to_pay_charlie(self):
|
|
bob, charlie = self.bob, self.charlie
|
|
chralie_group = VGroup(
|
|
charlie, charlie.label, charlie.ledger
|
|
)
|
|
|
|
self.play(
|
|
PiCreatureSays(
|
|
bob, "Did you hear that?",
|
|
target_mode = "sassy"
|
|
),
|
|
FadeIn(chralie_group)
|
|
)
|
|
self.play(charlie.change, "maybe", bob.eyes)
|
|
self.wait(2)
|
|
|
|
class YouListeningToBroadcasts(LedgerScene):
|
|
CONFIG = {
|
|
"denomination" : "LD"
|
|
}
|
|
def construct(self):
|
|
ledger = self.get_ledger()
|
|
payments = VGroup(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in [
|
|
("Alice", "You", 20),
|
|
("Bob", "You", 50),
|
|
("Charlie", "You", 30),
|
|
]
|
|
])
|
|
self.remove(self.ledger)
|
|
corners = [
|
|
FRAME_X_RADIUS*RIGHT*u1 + FRAME_Y_RADIUS*UP*u2
|
|
for u1, u2 in [(-1, 1), (1, 1), (-1, -1)]
|
|
]
|
|
you = self.you
|
|
you.scale(2)
|
|
you.center()
|
|
|
|
self.add(you)
|
|
for payment, corner in zip(payments, corners):
|
|
vect = corner/get_norm(corner)
|
|
payment.next_to(corner, vect)
|
|
self.play(
|
|
Broadcast(corner),
|
|
ApplyMethod(
|
|
payment.next_to, you, vect,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.3, 1)
|
|
),
|
|
you.change_mode, "pondering"
|
|
)
|
|
self.wait()
|
|
|
|
class AskWhatToAddToProtocol(InitialProtocol):
|
|
def construct(self):
|
|
self.add_title()
|
|
items = VGroup(*list(map(self.get_new_item, [
|
|
"Broadcast transactions",
|
|
"Only accept signed transactions",
|
|
"No overspending",
|
|
] + [""]*6)))
|
|
brace = Brace(VGroup(*items[3:]), LEFT)
|
|
question = TextMobject("What to \\\\ add here?")
|
|
question.set_color(RED)
|
|
question.scale(1.5)
|
|
brace.set_color(RED)
|
|
question.next_to(brace, LEFT)
|
|
|
|
self.add(*items[:3])
|
|
self.play(GrowFromCenter(brace))
|
|
self.play(Write(question))
|
|
self.wait()
|
|
|
|
class TrustComputationalWork(DistributedLedgerScene):
|
|
def construct(self):
|
|
self.add_ledger()
|
|
self.show_work()
|
|
|
|
def add_ledger(self):
|
|
ledgers = self.get_distributed_ledgers()
|
|
ledger = ledgers[0]
|
|
ledger.scale(3)
|
|
ledger[1].scale_in_place(2./3)
|
|
ledger.center().to_edge(UP).shift(4*LEFT)
|
|
plus = TexMobject("+")
|
|
plus.next_to(ledger, RIGHT)
|
|
|
|
self.add(ledger, plus)
|
|
self.ledger = ledger
|
|
self.plus = plus
|
|
|
|
def show_work(self):
|
|
zeros = TexMobject("0"*32)
|
|
zeros.next_to(self.plus, RIGHT)
|
|
brace = Brace(zeros, DOWN)
|
|
words = brace.get_text("Computational work")
|
|
self.add(brace, words)
|
|
|
|
for n in range(2**12):
|
|
binary = bin(n)[2:]
|
|
for i, bit_str in enumerate(reversed(binary)):
|
|
curr_bit = zeros.submobjects[-i-1]
|
|
new_bit = TexMobject(bit_str)
|
|
new_bit.replace(curr_bit, dim_to_match = 1)
|
|
if bit_str == "1":
|
|
new_bit.set_color(YELLOW)
|
|
zeros.submobjects[-i-1] = new_bit
|
|
self.remove(curr_bit)
|
|
self.add(zeros)
|
|
self.wait(1./30)
|
|
|
|
class TrustComputationalWorkSupplement(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"Main tool: ", "Cryptographic hash functions"
|
|
)
|
|
words[1].set_color(YELLOW)
|
|
self.add(words[0])
|
|
self.play(Write(words[1]))
|
|
self.wait()
|
|
|
|
class FraudIsInfeasible(Scene):
|
|
def construct(self):
|
|
words = TextMobject(
|
|
"Fraud", "$\\Leftrightarrow$",
|
|
"Computationally infeasible"
|
|
)
|
|
words.set_color_by_tex("Fraud", RED)
|
|
words.to_edge(UP)
|
|
self.play(FadeIn(words[0]))
|
|
self.play(FadeIn(words[2]))
|
|
self.play(Write(words[1]))
|
|
self.wait()
|
|
|
|
class ThisIsWellIntoTheWeeds(TeacherStudentsScene):
|
|
def construct(self):
|
|
idea = TextMobject("Proof of work")
|
|
idea.move_to(self.teacher.get_corner(UP+LEFT))
|
|
idea.shift(MED_LARGE_BUFF*UP)
|
|
idea.save_state()
|
|
lightbulb = Lightbulb()
|
|
lightbulb.next_to(idea, UP)
|
|
idea.shift(DOWN)
|
|
idea.set_fill(opacity = 0)
|
|
|
|
self.teacher_says(
|
|
"We're well into \\\\ the weeds now",
|
|
target_mode = "sassy",
|
|
added_anims = [
|
|
ApplyMethod(pi.change, mode)
|
|
for pi, mode in zip(self.students, [
|
|
"hooray", "sad", "erm"
|
|
])
|
|
],
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
idea.restore,
|
|
RemovePiCreatureBubble(
|
|
self.teacher, target_mode = "hooray",
|
|
look_at_arg = lightbulb
|
|
),
|
|
)
|
|
self.change_student_modes(
|
|
*["pondering"]*3,
|
|
added_anims = [OldLaggedStart(FadeIn, lightbulb)]
|
|
)
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, lightbulb,
|
|
lambda b : (b.set_color, YELLOW_A),
|
|
rate_func = there_and_back
|
|
))
|
|
self.wait(2)
|
|
|
|
class IntroduceSHA256(Scene):
|
|
def construct(self):
|
|
self.introduce_evaluation()
|
|
self.inverse_function_question()
|
|
self.issue_challenge()
|
|
self.shift_everything_down()
|
|
self.guess_and_check()
|
|
|
|
def introduce_evaluation(self):
|
|
messages = [
|
|
"3Blue1Brown",
|
|
"3Blue1Crown",
|
|
"Mathologer",
|
|
"Infinite Series",
|
|
"Numberphile",
|
|
"Welch Labs",
|
|
"3Blue1Brown",
|
|
]
|
|
groups = VGroup()
|
|
for message in messages:
|
|
lhs = TextMobject(
|
|
"SHA256", "(``", message, "'') =",
|
|
arg_separator = ""
|
|
)
|
|
lhs.set_color_by_tex(message, BLUE)
|
|
digest = sha256_tex_mob(message)
|
|
digest.next_to(lhs, RIGHT)
|
|
group = VGroup(lhs, digest)
|
|
group.to_corner(UP+RIGHT)
|
|
group.shift(MED_LARGE_BUFF*DOWN)
|
|
groups.add(group)
|
|
|
|
group = groups[0]
|
|
lhs, digest = group
|
|
sha, lp, message, lp = lhs
|
|
sha_brace = Brace(sha, UP)
|
|
message_brace = Brace(message, DOWN)
|
|
digest_brace = Brace(digest, DOWN)
|
|
sha_text = sha_brace.get_text("", "Hash function")
|
|
sha_text.set_color(YELLOW)
|
|
message_text = message_brace.get_text("Message/file")
|
|
message_text.set_color(BLUE)
|
|
digest_text = digest_brace.get_text("``Hash'' or ``Digest''")
|
|
brace_text_pairs = [
|
|
(sha_brace, sha_text),
|
|
(message_brace, message_text),
|
|
(digest_brace, digest_text),
|
|
]
|
|
|
|
looks_random = TextMobject("Looks random")
|
|
looks_random.set_color(MAROON_B)
|
|
looks_random.next_to(digest_text, DOWN)
|
|
|
|
self.add(group)
|
|
self.remove(digest)
|
|
for brace, text in brace_text_pairs:
|
|
if brace is digest_brace:
|
|
self.play(OldLaggedStart(
|
|
FadeIn, digest,
|
|
run_time = 4,
|
|
lag_ratio = 0.05
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(text, run_time = 2)
|
|
)
|
|
self.wait()
|
|
self.play(Write(looks_random))
|
|
self.wait(2)
|
|
for mob in digest, message:
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, mob,
|
|
lambda m : (m.set_color, YELLOW),
|
|
rate_func = there_and_back,
|
|
run_time = 1
|
|
))
|
|
self.wait()
|
|
self.play(FadeOut(looks_random))
|
|
|
|
new_lhs, new_digest = groups[1]
|
|
char = new_lhs[2][-5]
|
|
arrow = Arrow(UP, ORIGIN, buff = 0)
|
|
arrow.next_to(char, UP)
|
|
arrow.set_color(RED)
|
|
self.play(ShowCreation(arrow))
|
|
for new_group in groups[1:]:
|
|
new_lhs, new_digest = new_group
|
|
new_message = new_lhs[2]
|
|
self.play(
|
|
Transform(lhs, new_lhs),
|
|
message_brace.stretch_to_fit_width, new_message.get_width(),
|
|
message_brace.next_to, new_message, DOWN,
|
|
MaintainPositionRelativeTo(message_text, message_brace),
|
|
MaintainPositionRelativeTo(sha_brace, lhs[0]),
|
|
MaintainPositionRelativeTo(sha_text, sha_brace)
|
|
)
|
|
self.play(Transform(
|
|
digest, new_digest,
|
|
run_time = 2,
|
|
lag_ratio = 0.5,
|
|
path_arc = np.pi/2
|
|
))
|
|
if arrow in self.get_mobjects():
|
|
self.wait()
|
|
self.play(FadeOut(arrow))
|
|
self.wait()
|
|
|
|
new_sha_text = TextMobject(
|
|
"Cryptographic", "hash function"
|
|
)
|
|
new_sha_text.next_to(sha_brace, UP)
|
|
new_sha_text.shift_onto_screen()
|
|
new_sha_text.set_color(YELLOW)
|
|
new_sha_text[0].set_color(GREEN)
|
|
self.play(Transform(sha_text, new_sha_text))
|
|
self.wait()
|
|
|
|
self.lhs = lhs
|
|
self.message = message
|
|
self.digest = digest
|
|
self.digest_text = digest_text
|
|
self.message_text = message_text
|
|
|
|
def inverse_function_question(self):
|
|
arrow = Arrow(3*RIGHT, 3*LEFT, buff = 0)
|
|
arrow.set_stroke(width = 8)
|
|
arrow.set_color(RED)
|
|
everything = VGroup(*self.get_mobjects())
|
|
arrow.next_to(everything, DOWN)
|
|
words = TextMobject("Inverse is infeasible")
|
|
words.set_color(RED)
|
|
words.next_to(arrow, DOWN)
|
|
|
|
self.play(ShowCreation(arrow))
|
|
self.play(Write(words))
|
|
self.wait()
|
|
|
|
def issue_challenge(self):
|
|
desired_output_text = TextMobject("Desired output")
|
|
desired_output_text.move_to(self.digest_text)
|
|
desired_output_text.set_color(YELLOW)
|
|
new_digest = sha256_tex_mob("Challenge")
|
|
new_digest.replace(self.digest)
|
|
q_marks = TextMobject("???")
|
|
q_marks.move_to(self.message_text)
|
|
q_marks.set_color(BLUE)
|
|
|
|
self.play(
|
|
Transform(
|
|
self.digest, new_digest,
|
|
run_time = 2,
|
|
lag_ratio = 0.5,
|
|
path_arc = np.pi/2
|
|
),
|
|
Transform(self.digest_text, desired_output_text)
|
|
)
|
|
self.play(
|
|
FadeOut(self.message),
|
|
Transform(self.message_text, q_marks)
|
|
)
|
|
self.wait()
|
|
|
|
def shift_everything_down(self):
|
|
everything = VGroup(*self.get_top_level_mobjects())
|
|
self.play(
|
|
everything.scale, 0.85,
|
|
everything.to_edge, DOWN
|
|
)
|
|
|
|
def guess_and_check(self):
|
|
groups = VGroup()
|
|
for x in range(32):
|
|
message = "Guess \\#%d"%x
|
|
lhs = TextMobject(
|
|
"SHA256(``", message, "'') = ",
|
|
arg_separator = ""
|
|
)
|
|
lhs.set_color_by_tex("Guess", BLUE)
|
|
digest = sha256_tex_mob(message)
|
|
digest.next_to(lhs, RIGHT)
|
|
group = VGroup(lhs, digest)
|
|
group.scale(0.85)
|
|
group.next_to(self.digest, UP, aligned_edge = RIGHT)
|
|
group.to_edge(UP)
|
|
groups.add(group)
|
|
|
|
group = groups[0]
|
|
self.play(FadeIn(group))
|
|
for new_group in groups[1:]:
|
|
self.play(Transform(
|
|
group[0], new_group[0],
|
|
run_time = 0.5,
|
|
))
|
|
self.play(Transform(
|
|
group[1], new_group[1],
|
|
run_time = 1,
|
|
lag_ratio = 0.5
|
|
))
|
|
|
|
class PonderScematic(Scene):
|
|
def construct(self):
|
|
randy = Randolph()
|
|
randy.to_corner(DOWN+LEFT)
|
|
self.play(randy.change, "confused", ORIGIN)
|
|
for x in range(3):
|
|
self.play(Blink(randy))
|
|
self.wait(2)
|
|
|
|
class ViewingSLLCertificate(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class SHA256ToProofOfWork(TeacherStudentsScene):
|
|
def construct(self):
|
|
sha = TextMobject("SHA256")
|
|
proof = TextMobject("Proof of work")
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
group = VGroup(sha, arrow, proof)
|
|
group.arrange(RIGHT)
|
|
group.next_to(self.teacher, UP, buff = LARGE_BUFF)
|
|
group.to_edge(RIGHT, buff = LARGE_BUFF)
|
|
|
|
self.play(
|
|
Write(sha, run_time = 1),
|
|
self.teacher.change, "raise_right_hand"
|
|
)
|
|
self.play(ShowCreation(arrow))
|
|
self.play(Write(proof, run_time = 1))
|
|
self.wait(3)
|
|
|
|
class IntroduceNonceOnTrasactions(LedgerScene):
|
|
CONFIG = {
|
|
"denomination" : "LD",
|
|
"ledger_width" : 5,
|
|
"ledger_line_height" : 0.3,
|
|
}
|
|
def construct(self):
|
|
self.add(self.get_ledger())
|
|
self.hash_with_nonce()
|
|
self.write_probability()
|
|
self.guess_and_check()
|
|
self.name_proof_of_work()
|
|
self.change_ledger()
|
|
self.guess_and_check()
|
|
|
|
def hash_with_nonce(self):
|
|
ledger = self.ledger
|
|
self.add(*[
|
|
self.add_payment_line_to_ledger(*payment)
|
|
for payment in [
|
|
("Alice", "Bob", 20),
|
|
("Alice", "You", 30),
|
|
("Charlie", "You", 100),
|
|
]
|
|
])
|
|
|
|
nonce = TexMobject(str(2**30 + hash("Hey there")%(2**15)))
|
|
nonce.next_to(ledger, RIGHT, LARGE_BUFF)
|
|
nonce.set_color(GREEN_C)
|
|
nonce_brace = Brace(nonce, DOWN)
|
|
special_word = nonce_brace.get_text("Special number")
|
|
arrow = Arrow(LEFT, RIGHT, buff = 0)
|
|
arrow.next_to(ledger, RIGHT)
|
|
arrow.shift(MED_LARGE_BUFF*DOWN)
|
|
sha = TextMobject("SHA256")
|
|
sha.next_to(arrow, UP)
|
|
digest = sha256_tex_mob(
|
|
"""Man, you're reading this deeply into
|
|
the code behind videos? I'm touched,
|
|
really touched. Keeping loving math, my
|
|
friend. """,
|
|
n_forced_start_zeros = 30,
|
|
)
|
|
digest.next_to(arrow, RIGHT)
|
|
zeros = VGroup(*digest[:30])
|
|
zeros_brace = Brace(zeros, UP)
|
|
zeros_words = zeros_brace.get_text("30 zeros")
|
|
|
|
self.play(OldLaggedStart(
|
|
FadeIn, VGroup(special_word, nonce_brace, nonce)
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
nonce.next_to, ledger.content, DOWN, MED_SMALL_BUFF, LEFT,
|
|
FadeOut(special_word),
|
|
FadeOut(nonce_brace)
|
|
)
|
|
ledger.content.add(nonce)
|
|
decomposed_ledger = VGroup(*[m for m in ledger.family_members_with_points() if not m.is_subpath])
|
|
self.play(
|
|
ShowCreation(arrow),
|
|
FadeIn(sha)
|
|
)
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, decomposed_ledger,
|
|
lambda m : (m.set_color, YELLOW),
|
|
rate_func = there_and_back
|
|
))
|
|
point = VectorizedPoint(sha.get_center())
|
|
point.set_fill(opacity = 1)
|
|
self.play(OldLaggedStart(
|
|
Transform, decomposed_ledger.copy(),
|
|
lambda m : (m, point),
|
|
run_time = 1
|
|
))
|
|
bit_iter = iter(digest)
|
|
self.play(OldLaggedStart(
|
|
ReplacementTransform,
|
|
VGroup(*[point.copy() for x in range(256)]),
|
|
lambda m : (m, next(bit_iter)),
|
|
))
|
|
self.remove(*self.get_mobjects_from_last_animation())
|
|
self.add(digest)
|
|
self.play(
|
|
GrowFromCenter(zeros_brace),
|
|
Write(zeros_words, run_time = 1)
|
|
)
|
|
self.play(OldLaggedStart(
|
|
ApplyMethod, zeros,
|
|
lambda m : (m.set_color, YELLOW)
|
|
))
|
|
self.wait(2)
|
|
|
|
self.nonce = nonce
|
|
self.digest = digest
|
|
self.zeros_brace = zeros_brace
|
|
self.zeros_words = zeros_words
|
|
|
|
def write_probability(self):
|
|
probability = TextMobject(
|
|
"Probability: $\\frac{1}{2^{30}}$",
|
|
"$\\approx \\frac{1}{1{,}000{,}000{,}000}$",
|
|
)
|
|
probability.next_to(self.zeros_words, UP, MED_LARGE_BUFF)
|
|
|
|
self.play(FadeIn(probability[0]))
|
|
self.wait()
|
|
self.play(Write(probability[1], run_time = 2))
|
|
self.wait(2)
|
|
|
|
def guess_and_check(self):
|
|
q_mark = TexMobject("?")
|
|
q_mark.set_color(RED)
|
|
q_mark.next_to(self.zeros_words, RIGHT, SMALL_BUFF)
|
|
|
|
self.digest.save_state()
|
|
self.nonce.save_state()
|
|
|
|
self.play(FadeIn(q_mark))
|
|
for x in range(1, 13):
|
|
nonce = TexMobject(str(x))
|
|
nonce.move_to(self.nonce)
|
|
nonce.set_color(GREEN_C)
|
|
digest = sha256_tex_mob(str(x))
|
|
digest.replace(self.digest)
|
|
|
|
self.play(Transform(
|
|
self.nonce, nonce,
|
|
run_time = 1 if x == 1 else 0.3
|
|
))
|
|
self.play(Transform(
|
|
self.digest, digest,
|
|
run_time = 1,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.wait()
|
|
self.play(self.nonce.restore)
|
|
self.play(
|
|
self.digest.restore,
|
|
lag_ratio = 0.5,
|
|
run_time = 2
|
|
)
|
|
self.play(FadeOut(q_mark))
|
|
self.wait()
|
|
|
|
def name_proof_of_work(self):
|
|
words = TextMobject("``Proof of work''")
|
|
words.next_to(self.nonce, DOWN, LARGE_BUFF)
|
|
words.shift(MED_LARGE_BUFF*RIGHT)
|
|
words.set_color(GREEN)
|
|
arrow = Arrow(
|
|
words.get_top(), self.nonce.get_bottom(),
|
|
color = WHITE,
|
|
tip_length = 0.15
|
|
)
|
|
self.play(Write(words, run_time = 2))
|
|
self.play(ShowCreation(arrow))
|
|
self.wait()
|
|
|
|
def change_ledger(self):
|
|
amount = self.ledger.content[2][-1]
|
|
new_amount = TextMobject("300 LD")
|
|
new_amount.set_height(amount.get_height())
|
|
new_amount.set_color(amount.get_color())
|
|
new_amount.move_to(amount, LEFT)
|
|
|
|
new_digest = sha256_tex_mob("Ah shucks")
|
|
new_digest.replace(self.digest)
|
|
|
|
dot = Dot(amount.get_center())
|
|
dot.set_fill(opacity = 0.5)
|
|
|
|
self.play(FocusOn(amount))
|
|
self.play(Transform(amount, new_amount))
|
|
self.play(
|
|
dot.move_to, new_digest,
|
|
dot.set_fill, None, 0
|
|
)
|
|
self.play(Transform(
|
|
self.digest, new_digest,
|
|
lag_ratio = 0.5,
|
|
))
|
|
|
|
class ShowSomeBroadcasting(DistributedLedgerScene):
|
|
def construct(self):
|
|
self.add_large_network_and_distributed_ledger()
|
|
lines = self.network.lines.copy()
|
|
lines.add(*[
|
|
line.copy().rotate(np.pi)
|
|
for line in lines
|
|
])
|
|
|
|
point = VectorizedPoint(self.pi_creatures.get_center())
|
|
last_pi = None
|
|
for pi in self.pi_creatures:
|
|
outgoing_lines = []
|
|
for line in lines:
|
|
vect = line.get_start() - pi.get_center()
|
|
dist = get_norm(vect)
|
|
if dist < 2:
|
|
outgoing_lines.append(line)
|
|
dots = VGroup()
|
|
for line in outgoing_lines:
|
|
dot = Dot(line.get_start())
|
|
dot.set_color(YELLOW)
|
|
dot.generate_target()
|
|
dot.target.move_to(line.get_end())
|
|
for alt_pi in self.pi_creatures:
|
|
vect = line.get_end() - alt_pi.get_center()
|
|
dist = get_norm(vect)
|
|
if dist < 2:
|
|
dot.ledger = alt_pi.ledger
|
|
dots.add(dot)
|
|
self.play(
|
|
Animation(point),
|
|
Broadcast(pi),
|
|
*[
|
|
Succession(
|
|
FadeIn(dot),
|
|
MoveToTarget(dot, run_time = 2),
|
|
)
|
|
for dot in dots
|
|
]
|
|
)
|
|
self.play(*it.chain(*[
|
|
[dot.move_to, dot.ledger, dot.set_fill, None, 0]
|
|
for dot in dots
|
|
]))
|
|
|
|
class IntroduceBlockChain(Scene):
|
|
CONFIG = {
|
|
"transaction_color" : YELLOW,
|
|
"proof_of_work_color" : GREEN,
|
|
"prev_hash_color" : BLUE,
|
|
"block_width" : 3,
|
|
"block_height" : 3.5,
|
|
"n_transaction_lines" : 8,
|
|
"payment_height_to_block_height" : 0.15,
|
|
}
|
|
def setup(self):
|
|
ls = LedgerScene()
|
|
self.names = [
|
|
name.capitalize()
|
|
for name in ls.get_names()
|
|
]
|
|
self.name_colors = [
|
|
ls.get_color_from_name(name)
|
|
for name in self.names
|
|
]
|
|
|
|
def construct(self):
|
|
self.divide_ledger_into_blocks()
|
|
self.show_proofs_of_work()
|
|
self.chain_blocks_together()
|
|
self.mess_with_early_block()
|
|
self.propagate_hash_change()
|
|
self.redo_proof_of_work()
|
|
self.write_block_chain()
|
|
|
|
|
|
def divide_ledger_into_blocks(self):
|
|
blocks = VGroup(*[
|
|
self.get_block() for x in range(3)
|
|
])
|
|
blocks.arrange(RIGHT, buff = 1.5)
|
|
blocks.to_edge(UP)
|
|
|
|
all_payments = VGroup()
|
|
all_proofs_of_work = VGroup()
|
|
for block in blocks:
|
|
block.remove(block.prev_hash)
|
|
all_payments.add(*block.payments)
|
|
all_proofs_of_work.add(block.proof_of_work)
|
|
|
|
blocks_word = TextMobject("Blocks")
|
|
blocks_word.scale(1.5)
|
|
blocks_word.shift(2*DOWN)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
blocks_word.get_top(), block.get_bottom(),
|
|
buff = MED_LARGE_BUFF,
|
|
color = WHITE
|
|
)
|
|
for block in blocks
|
|
])
|
|
|
|
self.play(OldLaggedStart(FadeIn, blocks))
|
|
self.play(
|
|
Write(blocks_word),
|
|
OldLaggedStart(
|
|
ShowCreation, arrows,
|
|
run_time = 1,
|
|
lag_factor = 0.6,
|
|
)
|
|
)
|
|
self.wait()
|
|
for group in all_payments, all_proofs_of_work:
|
|
self.play(OldLaggedStart(
|
|
Indicate, group,
|
|
rate_func = there_and_back,
|
|
scale_factor = 1.1,
|
|
))
|
|
self.play(*list(map(FadeOut, [blocks_word, arrows])))
|
|
|
|
self.blocks = blocks
|
|
|
|
def show_proofs_of_work(self):
|
|
random.seed(0)
|
|
blocks = self.blocks
|
|
|
|
proofs_of_work = VGroup()
|
|
new_proofs_of_work = VGroup()
|
|
digests = VGroup()
|
|
arrows = VGroup()
|
|
sha_words = VGroup()
|
|
signatures = VGroup()
|
|
|
|
for block in blocks:
|
|
proofs_of_work.add(block.proof_of_work)
|
|
num_str = str(random.randint(0, 10**12))
|
|
number = TexMobject(num_str)
|
|
number.set_color(self.proof_of_work_color)
|
|
number.replace(block.proof_of_work, dim_to_match = 1)
|
|
new_proofs_of_work.add(number)
|
|
|
|
digest = sha256_tex_mob(num_str, 60)
|
|
digest.scale(0.7)
|
|
digest.move_to(block).to_edge(DOWN)
|
|
VGroup(*digest[:60]).set_color(YELLOW)
|
|
arrow = Arrow(block, digest)
|
|
sha = TextMobject("SHA256")
|
|
sha.scale(0.7)
|
|
point = arrow.get_center()
|
|
sha.next_to(point, UP, SMALL_BUFF)
|
|
sha.rotate(-np.pi/2, about_point = point)
|
|
sha.shift(SMALL_BUFF*(UP+RIGHT))
|
|
digests.add(digest)
|
|
arrows.add(arrow)
|
|
sha_words.add(sha)
|
|
|
|
for payment in block.payments[:2]:
|
|
signatures.add(payment[-1])
|
|
|
|
proofs_of_work.save_state()
|
|
|
|
self.play(Transform(
|
|
proofs_of_work, new_proofs_of_work,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.play(
|
|
ShowCreation(arrows),
|
|
Write(sha_words),
|
|
run_time = 2
|
|
)
|
|
self.play(Write(digests))
|
|
self.wait()
|
|
for group in signatures, proofs_of_work:
|
|
self.play(OldLaggedStart(
|
|
Indicate, group,
|
|
run_time = 2,
|
|
rate_func = there_and_back,
|
|
))
|
|
self.wait()
|
|
self.play(
|
|
proofs_of_work.restore,
|
|
FadeOut(sha_words)
|
|
)
|
|
|
|
self.digests = digests
|
|
self.sha_arrows = arrows
|
|
|
|
def chain_blocks_together(self):
|
|
blocks = self.blocks
|
|
digests = self.digests
|
|
sha_arrows = self.sha_arrows
|
|
block_spacing = blocks[1].get_center() - blocks[0].get_center()
|
|
prev_hashes = VGroup(*[
|
|
block.prev_hash for block in blocks
|
|
])
|
|
|
|
prev_hashes.add(
|
|
prev_hashes[-1].copy().shift(block_spacing).fade(1)
|
|
)
|
|
|
|
new_arrows = VGroup()
|
|
for block in blocks:
|
|
end = np.array([
|
|
block.get_left()[0] + block_spacing[0],
|
|
block.prev_hash.get_center()[1],
|
|
0
|
|
])
|
|
arrow = Arrow(end+LEFT, end, buff = SMALL_BUFF)
|
|
arrow.points[0] = block.get_right()
|
|
arrow.points[1] = block.get_right() + RIGHT
|
|
arrow.points[2] = end + LEFT + SMALL_BUFF*UP
|
|
new_arrows.add(arrow)
|
|
|
|
for i in range(3):
|
|
self.play(
|
|
ReplacementTransform(digests[i], prev_hashes[i+1]),
|
|
Transform(sha_arrows[i], new_arrows[i])
|
|
)
|
|
arrow = new_arrows[0].copy().shift(-block_spacing)
|
|
sha_arrows.add_to_back(arrow)
|
|
self.play(*list(map(FadeIn, [arrow, prev_hashes[0]])))
|
|
self.wait(2)
|
|
|
|
self.prev_hashes = prev_hashes
|
|
|
|
def mess_with_early_block(self):
|
|
blocks = self.blocks
|
|
amount = blocks[0].payments[1][3]
|
|
new_amount = TextMobject("400 LD")
|
|
new_amount.set_height(amount.get_height())
|
|
new_amount.set_color(RED)
|
|
new_amount.move_to(amount, LEFT)
|
|
|
|
self.play(FocusOn(amount))
|
|
self.play(Transform(amount, new_amount))
|
|
self.wait()
|
|
self.play(Swap(*blocks[:2]))
|
|
self.wait()
|
|
|
|
blocks.submobjects[:2] = blocks.submobjects[1::-1]
|
|
|
|
def propagate_hash_change(self):
|
|
prev_hashes = self.prev_hashes
|
|
|
|
for block, prev_hash in zip(self.blocks, prev_hashes[1:]):
|
|
rect = block.rect.copy()
|
|
rect.set_stroke(RED, 8)
|
|
rect.target = SurroundingRectangle(prev_hash)
|
|
rect.target.set_stroke(rect.get_color(), 0)
|
|
self.play(ShowCreation(rect))
|
|
self.play(
|
|
MoveToTarget(rect),
|
|
prev_hash.set_color, RED
|
|
)
|
|
|
|
def redo_proof_of_work(self):
|
|
proofs_of_work = VGroup(*[
|
|
block.proof_of_work for block in self.blocks
|
|
])
|
|
hashes = self.prev_hashes[1:]
|
|
|
|
self.play(FadeOut(proofs_of_work))
|
|
for proof_of_work, prev_hash in zip(proofs_of_work, hashes):
|
|
num_pow_group = VGroup(*[
|
|
Integer(random.randint(10**9, 10**10))
|
|
for x in range(50)
|
|
])
|
|
num_pow_group.set_color(proof_of_work.get_color())
|
|
num_pow_group.set_width(proof_of_work.get_width())
|
|
num_pow_group.move_to(proof_of_work)
|
|
for num_pow in num_pow_group:
|
|
self.add(num_pow)
|
|
self.wait(1./20)
|
|
prev_hash.set_color(random_bright_color())
|
|
self.remove(num_pow)
|
|
self.add(num_pow)
|
|
prev_hash.set_color(BLUE)
|
|
|
|
def write_block_chain(self):
|
|
ledger = TextMobject("Ledger")
|
|
ledger.next_to(self.blocks, DOWN, LARGE_BUFF)
|
|
cross = Cross(ledger)
|
|
block_chain = TextMobject("``Block Chain''")
|
|
block_chain.next_to(ledger, DOWN)
|
|
|
|
self.play(FadeIn(ledger))
|
|
self.play(
|
|
ShowCreation(cross),
|
|
Write(block_chain)
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
######
|
|
|
|
def get_block(self):
|
|
block = VGroup()
|
|
rect = Rectangle(
|
|
color = WHITE,
|
|
height = self.block_height,
|
|
width = self.block_width,
|
|
)
|
|
h_line1, h_line2 = [
|
|
Line(
|
|
rect.get_left(), rect.get_right()
|
|
).shift(0.3*rect.get_height()*vect)
|
|
for vect in (UP, DOWN)
|
|
]
|
|
|
|
payments = VGroup()
|
|
if not hasattr(self, "transaction_counter"):
|
|
self.transaction_counter = 0
|
|
for x in range(2):
|
|
hashes = [
|
|
hash("%d %d"%(seed, self.transaction_counter))
|
|
for seed in range(3)
|
|
]
|
|
payment = TextMobject(
|
|
self.names[hashes[0]%3],
|
|
"pays",
|
|
self.names[hashes[1]%4],
|
|
"%d0 LD"%(hashes[2]%9 + 1),
|
|
)
|
|
payment.set_color_by_tex("LD", YELLOW)
|
|
for name, color in zip(self.names, self.name_colors):
|
|
payment.set_color_by_tex(name, color)
|
|
signature = TextMobject("$\\langle$ Signature $\\rangle$")
|
|
signature.set_color(payment[0].get_color())
|
|
signature.next_to(payment, DOWN, SMALL_BUFF)
|
|
payment.add(signature)
|
|
|
|
factor = self.payment_height_to_block_height
|
|
payment.set_height(factor*rect.get_height())
|
|
payments.add(payment)
|
|
self.transaction_counter += 1
|
|
payments.add(TexMobject("\\dots").scale(0.5))
|
|
payments.arrange(DOWN, buff = MED_SMALL_BUFF)
|
|
payments.next_to(h_line1, DOWN)
|
|
|
|
proof_of_work = TextMobject("Proof of work")
|
|
proof_of_work.set_color(self.proof_of_work_color)
|
|
proof_of_work.scale(0.8)
|
|
proof_of_work.move_to(
|
|
VGroup(h_line2, VectorizedPoint(rect.get_bottom()))
|
|
)
|
|
|
|
prev_hash = TextMobject("Prev hash")
|
|
prev_hash.scale(0.8)
|
|
prev_hash.set_color(self.prev_hash_color)
|
|
prev_hash.move_to(
|
|
VGroup(h_line1, VectorizedPoint(rect.get_top()))
|
|
)
|
|
|
|
block.rect = rect
|
|
block.h_lines = VGroup(h_line1, h_line2)
|
|
block.payments = payments
|
|
block.proof_of_work = proof_of_work
|
|
block.prev_hash = prev_hash
|
|
block.digest_mobject_attrs()
|
|
return block
|
|
|
|
class DistributedBlockChainScene(DistributedLedgerScene):
|
|
CONFIG = {
|
|
"block_height" : 0.5,
|
|
"block_width" : 0.5,
|
|
"n_blocks" : 3,
|
|
}
|
|
def get_distributed_ledgers(self):
|
|
ledgers = VGroup()
|
|
point = self.pi_creatures.get_center()
|
|
for pi in self.pi_creatures:
|
|
vect = pi.get_center() - point
|
|
vect[0] = 0
|
|
block_chain = self.get_block_chain()
|
|
block_chain.next_to(
|
|
VGroup(pi, pi.label), vect, SMALL_BUFF
|
|
)
|
|
pi.block_chain = pi.ledger = block_chain
|
|
ledgers.add(block_chain)
|
|
self.ledgers = self.block_chains = ledgers
|
|
return ledgers
|
|
|
|
def get_block_chain(self):
|
|
blocks = VGroup(*[
|
|
self.get_block()
|
|
for x in range(self.n_blocks)
|
|
])
|
|
blocks.arrange(RIGHT, buff = MED_SMALL_BUFF)
|
|
arrows = VGroup()
|
|
|
|
for b1, b2 in zip(blocks, blocks[1:]):
|
|
arrow = Arrow(
|
|
LEFT, RIGHT,
|
|
preserve_tip_size_when_scaling = False,
|
|
tip_length = 0.15,
|
|
)
|
|
arrow.set_width(b1.get_width())
|
|
target_point = interpolate(
|
|
b2.get_left(), b2.get_corner(UP+LEFT), 0.8
|
|
)
|
|
arrow.next_to(target_point, LEFT, 0.5*SMALL_BUFF)
|
|
arrow.points[0] = b1.get_right()
|
|
arrow.points[1] = b2.get_left()
|
|
arrow.points[2] = b1.get_corner(UP+RIGHT)
|
|
arrow.points[2] += SMALL_BUFF*LEFT
|
|
arrows.add(arrow)
|
|
block_chain = VGroup(blocks, arrows)
|
|
block_chain.blocks = blocks
|
|
block_chain.arrows = arrows
|
|
return block_chain
|
|
|
|
def get_block(self):
|
|
block = Rectangle(
|
|
color = WHITE,
|
|
height = self.block_height,
|
|
width = self.block_width,
|
|
)
|
|
for vect in UP, DOWN:
|
|
line = Line(block.get_left(), block.get_right())
|
|
line.shift(0.3*block.get_height()*vect)
|
|
block.add(line)
|
|
return block
|
|
|
|
def create_pi_creatures(self):
|
|
creatures = DistributedLedgerScene.create_pi_creatures(self)
|
|
VGroup(
|
|
self.alice, self.alice.label,
|
|
self.charlie, self.charlie.label,
|
|
).shift(LEFT)
|
|
return creatures
|
|
|
|
#Out of order
|
|
class FromBankToDecentralizedSystem(DistributedBlockChainScene):
|
|
CONFIG = {
|
|
"n_blocks" : 5,
|
|
"ledger_height" : 3,
|
|
}
|
|
def construct(self):
|
|
self.remove_bank()
|
|
self.show_block_chains()
|
|
self.add_crypto_terms()
|
|
self.set_aside_everything()
|
|
|
|
def remove_bank(self):
|
|
bank = SVGMobject(
|
|
file_name = "bank_building",
|
|
color = LIGHT_GREY,
|
|
height = 3,
|
|
)
|
|
cross = Cross(bank)
|
|
cross.set_stroke(width = 10)
|
|
group = VGroup(bank, cross)
|
|
|
|
self.play(OldLaggedStart(DrawBorderThenFill, bank))
|
|
self.play(ShowCreation(cross))
|
|
self.wait()
|
|
self.play(
|
|
group.next_to, FRAME_X_RADIUS*RIGHT, RIGHT,
|
|
rate_func = running_start,
|
|
path_arc = -np.pi/6,
|
|
)
|
|
self.wait()
|
|
self.remove(group)
|
|
|
|
def show_block_chains(self):
|
|
creatures = self.pi_creatures
|
|
creatures.center()
|
|
VGroup(self.charlie, self.you).to_edge(DOWN)
|
|
chains = self.get_distributed_ledgers()
|
|
for pi, chain in zip(creatures, chains):
|
|
pi.scale_in_place(1.5)
|
|
pi.shift(0.5*pi.get_center()[0]*RIGHT)
|
|
chain.next_to(pi, UP)
|
|
center_chain = self.get_block_chain()
|
|
center_chain.scale(2)
|
|
center_chain.center()
|
|
|
|
self.play(OldLaggedStart(FadeIn, creatures, run_time = 1))
|
|
self.play(
|
|
OldLaggedStart(FadeIn, center_chain.blocks, run_time = 1),
|
|
ShowCreation(center_chain.arrows),
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
ReplacementTransform(VGroup(center_chain), chains),
|
|
*[
|
|
ApplyMethod(pi.change, "pondering", pi.ledger)
|
|
for pi in creatures
|
|
]
|
|
)
|
|
self.wait()
|
|
|
|
def add_crypto_terms(self):
|
|
terms = TextMobject(
|
|
"Digital signatures \\\\",
|
|
"Cryptographic hash functions",
|
|
)
|
|
terms.set_color_by_tex("signature", BLUE)
|
|
terms.set_color_by_tex("hash", YELLOW)
|
|
for term in terms:
|
|
self.play(Write(term, run_time = 1))
|
|
self.wait()
|
|
self.digital_signature = terms[0]
|
|
|
|
def set_aside_everything(self):
|
|
digital_signature = self.digital_signature
|
|
ledger = LedgerScene.get_ledger(self)
|
|
LedgerScene.add_payment_line_to_ledger(self,
|
|
"Alice", "Bob", "40",
|
|
)
|
|
LedgerScene.add_payment_line_to_ledger(self,
|
|
"Charlie", "Alice", "60",
|
|
)
|
|
ledger.next_to(ORIGIN, LEFT)
|
|
|
|
self.remove(digital_signature)
|
|
everything = VGroup(*self.get_top_level_mobjects())
|
|
|
|
self.play(
|
|
Animation(digital_signature),
|
|
everything.scale, 0.1,
|
|
everything.to_corner, DOWN+LEFT,
|
|
)
|
|
self.play(
|
|
Write(ledger),
|
|
digital_signature.next_to, ORIGIN, RIGHT
|
|
)
|
|
self.wait(2)
|
|
|
|
class IntroduceBlockCreator(DistributedBlockChainScene):
|
|
CONFIG = {
|
|
"n_block_creators" : 3,
|
|
"n_pow_guesses" : 60,
|
|
}
|
|
def construct(self):
|
|
self.add_network()
|
|
self.add_block_creators()
|
|
self.broadcast_transactions()
|
|
self.collect_transactions()
|
|
self.find_proof_of_work()
|
|
self.add_block_reward()
|
|
self.comment_on_block_reward()
|
|
self.write_miners()
|
|
self.broadcast_block()
|
|
|
|
def add_network(self):
|
|
network = self.get_large_network()
|
|
network.remove(network.lines)
|
|
network.scale(0.7)
|
|
ledgers = self.get_distributed_ledgers()
|
|
self.add(network, ledgers)
|
|
VGroup(network, ledgers).to_edge(RIGHT)
|
|
|
|
def add_block_creators(self):
|
|
block_creators = VGroup()
|
|
labels = VGroup()
|
|
everything = VGroup()
|
|
for x in range(self.n_block_creators):
|
|
block_creator = PiCreature(color = GREY)
|
|
block_creator.set_height(self.alice.get_height())
|
|
label = TextMobject("Block creator %d"%(x+1))
|
|
label.scale(0.7)
|
|
label.next_to(block_creator, DOWN, SMALL_BUFF)
|
|
block_creator.label = label
|
|
block_creators.add(block_creator)
|
|
labels.add(label)
|
|
everything.add(VGroup(block_creator, label))
|
|
everything.arrange(DOWN, buff = LARGE_BUFF)
|
|
everything.to_edge(LEFT)
|
|
|
|
self.play(OldLaggedStart(FadeIn, everything))
|
|
self.pi_creatures.add(*block_creators)
|
|
self.wait()
|
|
|
|
self.block_creators = block_creators
|
|
self.block_creator_labels = labels
|
|
|
|
def broadcast_transactions(self):
|
|
payment_parts = [
|
|
("Alice", "Bob", 20),
|
|
("Bob", "Charlie", 10),
|
|
("Charlie", "You", 50),
|
|
("You", "Alice", 30),
|
|
]
|
|
payments = VGroup()
|
|
payment_targets = VGroup()
|
|
for from_name, to_name, amount in payment_parts:
|
|
verb = "pay" if from_name == "You" else "pays"
|
|
payment = TextMobject(
|
|
from_name, verb, to_name, "%d LD"%amount
|
|
)
|
|
payment.set_color_by_tex("LD", YELLOW)
|
|
for name in self.get_names():
|
|
payment.set_color_by_tex(
|
|
name.capitalize(),
|
|
self.get_color_from_name(name)
|
|
)
|
|
payment.scale(0.7)
|
|
payment.generate_target()
|
|
payment_targets.add(payment.target)
|
|
|
|
pi = getattr(self, from_name.lower())
|
|
payment.scale(0.1)
|
|
payment.set_fill(opacity = 0)
|
|
payment.move_to(pi)
|
|
payments.add(payment)
|
|
payment_targets.arrange(DOWN, aligned_edge = LEFT)
|
|
payment_targets.next_to(
|
|
self.block_creator_labels, RIGHT,
|
|
MED_LARGE_BUFF
|
|
)
|
|
payment_targets.shift(UP)
|
|
|
|
anims = []
|
|
alpha_range = np.linspace(0, 0.5, len(payments))
|
|
for pi, payment, alpha in zip(self.pi_creatures, payments, alpha_range):
|
|
rf1 = squish_rate_func(smooth, alpha, alpha+0.5)
|
|
rf2 = squish_rate_func(smooth, alpha, alpha+0.5)
|
|
anims.append(Broadcast(
|
|
pi, rate_func = rf1,
|
|
big_radius = 3,
|
|
))
|
|
anims.append(MoveToTarget(payment, rate_func = rf2))
|
|
|
|
self.play(*anims, run_time = 5)
|
|
self.payments = payments
|
|
|
|
def collect_transactions(self):
|
|
creator = self.block_creators[0]
|
|
block = self.get_block()
|
|
block.stretch_to_fit_height(4)
|
|
block.stretch_to_fit_width(3.5)
|
|
block.next_to(creator.label, RIGHT, MED_LARGE_BUFF)
|
|
block.to_edge(UP)
|
|
|
|
payments = self.payments
|
|
payments.generate_target()
|
|
payments.target.set_height(1.5)
|
|
payments.target.move_to(block)
|
|
|
|
prev_hash = TextMobject("Prev hash")
|
|
prev_hash.set_color(BLUE)
|
|
prev_hash.set_height(0.3)
|
|
prev_hash.next_to(block.get_top(), DOWN, MED_SMALL_BUFF)
|
|
block.add(prev_hash)
|
|
|
|
self.play(
|
|
FadeIn(block),
|
|
MoveToTarget(payments),
|
|
creator.change, "raise_right_hand"
|
|
)
|
|
self.wait()
|
|
block.add(payments)
|
|
|
|
self.block = block
|
|
|
|
def find_proof_of_work(self):
|
|
block = self.block
|
|
|
|
arrow = Arrow(UP, ORIGIN, buff = 0)
|
|
arrow.next_to(block, DOWN)
|
|
sha = TextMobject("SHA256")
|
|
sha.scale(0.7)
|
|
sha.next_to(arrow, RIGHT)
|
|
arrow.add(sha)
|
|
|
|
self.add(arrow)
|
|
for x in range(self.n_pow_guesses):
|
|
guess = Integer(random.randint(10**11, 10**12))
|
|
guess.set_color(GREEN)
|
|
guess.set_height(0.3)
|
|
guess.next_to(block.get_bottom(), UP, MED_SMALL_BUFF)
|
|
|
|
if x == self.n_pow_guesses - 1:
|
|
digest = sha256_tex_mob(str(x), 60)
|
|
VGroup(*digest[:60]).set_color(YELLOW)
|
|
else:
|
|
digest = sha256_tex_mob(str(x))
|
|
digest.set_width(block.get_width())
|
|
digest.next_to(arrow.get_end(), DOWN)
|
|
|
|
self.add(guess, digest)
|
|
self.wait(1./20)
|
|
self.remove(guess, digest)
|
|
proof_of_work = guess
|
|
self.add(proof_of_work, digest)
|
|
block.add(proof_of_work)
|
|
self.wait()
|
|
|
|
self.hash_group = VGroup(arrow, digest)
|
|
|
|
def add_block_reward(self):
|
|
payments = self.payments
|
|
new_transaction = TextMobject(
|
|
self.block_creator_labels[0].get_tex_string(),
|
|
"gets", "10 LD"
|
|
)
|
|
new_transaction[0].set_color(LIGHT_GREY)
|
|
new_transaction.set_color_by_tex("LD", YELLOW)
|
|
new_transaction.set_height(payments[0].get_height())
|
|
new_transaction.move_to(payments.get_top())
|
|
payments.generate_target()
|
|
payments.target.next_to(new_transaction, DOWN, SMALL_BUFF, LEFT)
|
|
new_transaction.shift(SMALL_BUFF*UP)
|
|
|
|
self.play(
|
|
MoveToTarget(payments),
|
|
Write(new_transaction)
|
|
)
|
|
payments.add_to_back(new_transaction)
|
|
self.wait()
|
|
|
|
def comment_on_block_reward(self):
|
|
reward = self.payments[0]
|
|
reward_rect = SurroundingRectangle(reward)
|
|
big_rect = SurroundingRectangle(self.ledgers)
|
|
big_rect.set_stroke(width = 0)
|
|
big_rect.set_fill(BLACK, opacity = 1)
|
|
|
|
comments = VGroup(*list(map(TextMobject, [
|
|
"- ``Block reward''",
|
|
"- No sender/signature",
|
|
"- Adds to total money supply",
|
|
])))
|
|
comments.arrange(DOWN, aligned_edge = LEFT)
|
|
comments.move_to(big_rect, UP+LEFT)
|
|
|
|
pi_creatures = self.pi_creatures
|
|
self.pi_creatures = VGroup()
|
|
|
|
self.play(ShowCreation(reward_rect))
|
|
self.play(FadeIn(big_rect))
|
|
for comment in comments:
|
|
self.play(FadeIn(comment))
|
|
self.wait(2)
|
|
self.play(*list(map(FadeOut, [big_rect, comments, reward_rect])))
|
|
|
|
self.pi_creatures = pi_creatures
|
|
|
|
def write_miners(self):
|
|
for label in self.block_creator_labels:
|
|
tex = label.get_tex_string()
|
|
new_label = TextMobject("Miner " + tex[-1])
|
|
new_label.set_color(label.get_color())
|
|
new_label.replace(label, dim_to_match = 1)
|
|
self.play(Transform(label, new_label))
|
|
top_payment = self.payments[0]
|
|
new_top_payment = TextMobject("Miner 1", "gets", "10 LD")
|
|
new_top_payment[0].set_color(LIGHT_GREY)
|
|
new_top_payment[-1].set_color(YELLOW)
|
|
new_top_payment.set_height(top_payment.get_height())
|
|
new_top_payment.move_to(top_payment, LEFT)
|
|
self.play(Transform(top_payment, new_top_payment))
|
|
self.wait()
|
|
|
|
def broadcast_block(self):
|
|
old_chains = self.block_chains
|
|
self.n_blocks = 4
|
|
new_chains = self.get_distributed_ledgers()
|
|
block_target_group = VGroup()
|
|
anims = []
|
|
arrow_creations = []
|
|
for old_chain, new_chain in zip(old_chains, new_chains):
|
|
for attr in "blocks", "arrows":
|
|
pairs = list(zip(
|
|
getattr(old_chain, attr),
|
|
getattr(new_chain, attr),
|
|
))
|
|
for m1, m2 in pairs:
|
|
anims.append(Transform(m1, m2))
|
|
arrow_creations.append(ShowCreation(new_chain.arrows[-1]))
|
|
block_target = self.block.copy()
|
|
block_target.replace(new_chain.blocks[-1], stretch = True)
|
|
block_target_group.add(block_target)
|
|
anims.append(Transform(
|
|
VGroup(self.block),
|
|
block_target_group
|
|
))
|
|
anims.append(Broadcast(self.block, n_circles = 4))
|
|
anims.append(FadeOut(self.hash_group))
|
|
anims.append(ApplyMethod(
|
|
self.block_creators[0].change, "happy"
|
|
))
|
|
|
|
self.play(*anims, run_time = 2)
|
|
self.play(*it.chain(
|
|
arrow_creations,
|
|
[
|
|
ApplyMethod(
|
|
pi.change, "hooray",
|
|
pi.block_chain.get_right()
|
|
)
|
|
for pi in self.pi_creatures
|
|
]
|
|
))
|
|
self.wait(3)
|
|
|
|
class MiningIsALottery(IntroduceBlockCreator):
|
|
CONFIG = {
|
|
"n_miners" : 3,
|
|
"denomination" : "LD",
|
|
"n_guesses" : 90,
|
|
"n_nonce_digits" : 15,
|
|
}
|
|
def construct(self):
|
|
self.add_blocks()
|
|
self.add_arrows()
|
|
self.make_guesses()
|
|
|
|
def create_pi_creatures(self):
|
|
IntroduceBlockCreator.create_pi_creatures(self)
|
|
miners = VGroup(*[
|
|
PiCreature(color = GREY)
|
|
for n in range(self.n_miners)
|
|
])
|
|
miners.scale(0.5)
|
|
miners.arrange(DOWN, buff = LARGE_BUFF)
|
|
miners.to_edge(LEFT)
|
|
for x, miner in enumerate(miners):
|
|
label = TextMobject("Miner %d"%(x+1))
|
|
label.scale(0.7)
|
|
label.next_to(miner, DOWN, SMALL_BUFF)
|
|
miner.label = label
|
|
self.add(label)
|
|
self.miners = miners
|
|
return miners
|
|
|
|
def add_blocks(self):
|
|
self.add(self.miners)
|
|
|
|
blocks = VGroup()
|
|
for miner in self.miners:
|
|
block = self.get_block()
|
|
block.stretch_to_fit_height(2)
|
|
block.stretch_to_fit_width(3)
|
|
block.next_to(miner, RIGHT)
|
|
|
|
payments = self.get_payments(miner)
|
|
payments.set_height(1)
|
|
payments.move_to(block)
|
|
block.add(payments)
|
|
|
|
prev_hash = TextMobject("Prev hash")
|
|
prev_hash.set_color(BLUE)
|
|
prev_hash.set_height(0.2)
|
|
prev_hash.next_to(block.get_top(), DOWN, SMALL_BUFF)
|
|
block.add(prev_hash)
|
|
|
|
miner.block = block
|
|
miner.change("pondering", block)
|
|
blocks.add(block)
|
|
|
|
self.blocks = blocks
|
|
self.add(blocks)
|
|
|
|
def add_arrows(self):
|
|
self.arrows = VGroup()
|
|
for block in self.blocks:
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
arrow.next_to(block)
|
|
label = TextMobject("SHA256")
|
|
label.scale(0.7)
|
|
label.next_to(arrow, UP, buff = SMALL_BUFF)
|
|
self.add(arrow, label)
|
|
block.arrow = arrow
|
|
self.arrows.add(VGroup(arrow, label))
|
|
|
|
def make_guesses(self):
|
|
for x in range(self.n_guesses):
|
|
e = self.n_nonce_digits
|
|
nonces = VGroup()
|
|
digests = VGroup()
|
|
for block in self.blocks:
|
|
nonce = Integer(random.randint(10**e, 10**(e+1)))
|
|
nonce.set_height(0.2)
|
|
nonce.next_to(block.get_bottom(), UP, SMALL_BUFF)
|
|
nonces.add(nonce)
|
|
digest = sha256_tex_mob(str(x) + str(block))
|
|
digest.set_height(block.get_height())
|
|
digest.next_to(block.arrow, RIGHT)
|
|
digests.add(digest)
|
|
self.add(nonces, digests)
|
|
self.wait(1./20)
|
|
self.remove(nonces, digests)
|
|
self.add(nonces, digests)
|
|
|
|
winner_index = 1
|
|
winner = self.miners[winner_index]
|
|
losers = VGroup(*[m for m in self.miners if m is not winner])
|
|
|
|
nonces[winner_index].set_color(GREEN)
|
|
new_digest = sha256_tex_mob("Winner", 60)
|
|
VGroup(*new_digest[:60]).set_color(YELLOW)
|
|
old_digest = digests[winner_index]
|
|
new_digest.replace(old_digest)
|
|
Transform(old_digest, new_digest).update(1)
|
|
|
|
self.play(
|
|
winner.change, "hooray",
|
|
*[
|
|
ApplyMethod(VGroup(
|
|
self.blocks[i], self.arrows[i],
|
|
nonces[i], digests[i]
|
|
).fade, 0.7)
|
|
for i in range(len(self.blocks))
|
|
if i is not winner_index
|
|
]
|
|
)
|
|
self.play(*[
|
|
ApplyMethod(loser.change, "angry", winner)
|
|
for loser in losers
|
|
])
|
|
self.wait(2)
|
|
|
|
#####
|
|
|
|
def get_payments(self, miner):
|
|
if not hasattr(self, "ledger"):
|
|
self.get_ledger() ##Unused
|
|
self.ledger.content.remove(*self.ledger.content[1:])
|
|
|
|
lines = VGroup()
|
|
|
|
miner_name = miner.label.get_tex_string()
|
|
top_line = TextMobject(miner_name, "gets", "10 LD")
|
|
top_line.set_color_by_tex(miner_name, LIGHT_GREY)
|
|
top_line.set_color_by_tex("LD", YELLOW)
|
|
lines.add(top_line)
|
|
payments = [
|
|
("Alice", "Bob", 20),
|
|
("Charlie", "You", 50),
|
|
]
|
|
for payment in payments:
|
|
lines.add(self.add_payment_line_to_ledger(*payment))
|
|
lines.add(TexMobject("\\vdots"))
|
|
for line in lines:
|
|
line.set_height(0.5)
|
|
lines.arrange(
|
|
DOWN, buff = SMALL_BUFF, aligned_edge = LEFT
|
|
)
|
|
lines[-1].next_to(lines[-2], DOWN, buff = SMALL_BUFF)
|
|
return lines
|
|
|
|
class TwoBlockChains(DistributedBlockChainScene):
|
|
CONFIG = {
|
|
"n_blocks" : 5,
|
|
}
|
|
def construct(self):
|
|
self.listen_for_new_blocks()
|
|
self.defer_to_longer()
|
|
self.break_tie()
|
|
|
|
def listen_for_new_blocks(self):
|
|
randy = self.randy
|
|
chain = self.get_block_chain()
|
|
chain.scale(1.5)
|
|
chain.next_to(randy, UP+RIGHT)
|
|
chain.shift_onto_screen()
|
|
randy.change("raise_right_hand", chain)
|
|
|
|
corners = [
|
|
u1 * FRAME_X_RADIUS*RIGHT + u2 * FRAME_Y_RADIUS*UP
|
|
for u1, u2 in it.product(*[[-1, 1]]*2)
|
|
]
|
|
moving_blocks = chain.blocks[1:]
|
|
|
|
self.add(randy, chain.blocks[0])
|
|
for corner, block, arrow in zip(corners, moving_blocks, chain.arrows):
|
|
block.save_state()
|
|
block.next_to(corner, corner)
|
|
self.play(
|
|
ApplyMethod(
|
|
block.restore,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.8),
|
|
run_time = 3,
|
|
),
|
|
Broadcast(corner, run_time = 3),
|
|
ShowCreation(
|
|
arrow,
|
|
rate_func = squish_rate_func(smooth, 0.8, 1),
|
|
run_time = 3,
|
|
),
|
|
)
|
|
self.wait()
|
|
|
|
self.block_chain = chain
|
|
|
|
def defer_to_longer(self):
|
|
randy = self.randy
|
|
self.n_blocks -= 1
|
|
block_chains = VGroup(
|
|
self.block_chain,
|
|
self.get_block_chain().scale(1.5)
|
|
)
|
|
block_chains[1].next_to(randy, UP+LEFT)
|
|
block_chains[1].shift_onto_screen()
|
|
|
|
conflicting = TextMobject("Conflicting")
|
|
conflicting.to_edge(UP)
|
|
conflicting.set_color(RED)
|
|
arrows = VGroup(*[
|
|
Arrow(
|
|
conflicting.get_bottom(), block_chain.get_top(),
|
|
color = RED,
|
|
buff = MED_LARGE_BUFF
|
|
)
|
|
for block_chain in block_chains
|
|
])
|
|
|
|
longer_chain_rect = SurroundingRectangle(block_chains[0])
|
|
longer_chain_rect.set_stroke(GREEN, 8)
|
|
checkmark = TexMobject("\\checkmark")
|
|
checkmark.set_color(GREEN)
|
|
checkmark.next_to(longer_chain_rect, UP)
|
|
checkmark.shift(RIGHT)
|
|
|
|
chain = block_chains[1]
|
|
chain.save_state()
|
|
corner = FRAME_X_RADIUS*LEFT + FRAME_Y_RADIUS*UP
|
|
chain.next_to(corner, UP+LEFT)
|
|
self.play(
|
|
randy.change, "confused", chain,
|
|
Broadcast(corner),
|
|
ApplyMethod(
|
|
chain.restore,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.7),
|
|
run_time = 3
|
|
)
|
|
)
|
|
self.play(
|
|
Write(conflicting),
|
|
*list(map(ShowCreation, arrows))
|
|
)
|
|
self.wait()
|
|
self.play(ShowCreation(longer_chain_rect))
|
|
self.play(Write(checkmark, run_time = 1))
|
|
self.play(randy.change, "thinking", checkmark)
|
|
self.wait()
|
|
|
|
self.to_fade = VGroup(
|
|
conflicting, arrows,
|
|
longer_chain_rect, checkmark
|
|
)
|
|
self.block_chains = block_chains
|
|
|
|
def break_tie(self):
|
|
to_fade = self.to_fade
|
|
block_chains = self.block_chains
|
|
randy = self.randy
|
|
|
|
arrow = block_chains[1].arrows[-1]
|
|
block = block_chains[1].blocks[-1]
|
|
arrow_block = VGroup(arrow, block).copy()
|
|
|
|
block_chains.generate_target()
|
|
block_chains.target.arrange(
|
|
DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT
|
|
)
|
|
block_chains.target.next_to(randy, UP)
|
|
block_chains.target.to_edge(LEFT)
|
|
|
|
self.play(
|
|
MoveToTarget(block_chains),
|
|
FadeOut(to_fade),
|
|
run_time = 1
|
|
)
|
|
arrow_block.next_to(block_chains[1], RIGHT, buff = 0)
|
|
block_chains[1].add(arrow_block)
|
|
self.play(
|
|
randy.change, "confused", block_chains,
|
|
FadeIn(arrow_block),
|
|
)
|
|
self.wait()
|
|
|
|
arrow_block = arrow_block.copy()
|
|
arrow_block.next_to(FRAME_X_RADIUS*RIGHT, RIGHT)
|
|
self.play(
|
|
ApplyMethod(
|
|
arrow_block.next_to, block_chains[0], RIGHT, 0,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.8)
|
|
),
|
|
Broadcast(arrow_block),
|
|
)
|
|
block_chains[0].add(arrow_block)
|
|
rect = SurroundingRectangle(block_chains[0])
|
|
rect.set_stroke(GREEN, 8)
|
|
checkmark = TexMobject("\\checkmark")
|
|
checkmark.next_to(rect, UP)
|
|
checkmark.set_color(GREEN)
|
|
|
|
self.play(
|
|
ShowCreation(rect),
|
|
Write(checkmark),
|
|
randy.change, "happy", arrow_block
|
|
)
|
|
self.wait(2)
|
|
|
|
|
|
####
|
|
|
|
def create_pi_creatures(self):
|
|
randy = Randolph()
|
|
randy.to_edge(DOWN)
|
|
self.randy = randy
|
|
return VGroup(randy)
|
|
|
|
class ReplaceCentralAuthorityWithWork(Scene):
|
|
def construct(self):
|
|
trust, central = words = TextMobject("Trust", "central authority")
|
|
words.scale(1.5)
|
|
cross = Cross(central)
|
|
work = TextMobject("computational work")
|
|
work.scale(1.5)
|
|
work.move_to(central, LEFT)
|
|
work.set_color(YELLOW)
|
|
|
|
self.play(Write(words))
|
|
self.play(ShowCreation(cross))
|
|
central.add(cross)
|
|
self.play(
|
|
central.shift, DOWN,
|
|
Write(work)
|
|
)
|
|
self.wait()
|
|
|
|
class AskAboutTrustingWork(TeacherStudentsScene):
|
|
def construct(self):
|
|
mode = "raise_left_hand"
|
|
self.student_says(
|
|
"Is trusting work \\\\ really enough?",
|
|
target_mode = mode,
|
|
)
|
|
self.change_student_modes("confused", mode, "erm")
|
|
self.wait(3)
|
|
self.teacher_says(
|
|
"Well, let's try\\\\ fooling someone",
|
|
target_mode = "speaking"
|
|
)
|
|
self.wait(2)
|
|
|
|
class DoubleSpendingAttack(DistributedBlockChainScene):
|
|
CONFIG = {
|
|
"fraud_block_height" : 3,
|
|
}
|
|
def construct(self):
|
|
self.initialize_characters()
|
|
self.show_fraudulent_block()
|
|
self.send_to_bob()
|
|
self.dont_send_to_rest_of_network()
|
|
|
|
def initialize_characters(self):
|
|
network = self.get_large_network()
|
|
network.scale(0.7)
|
|
block_chains = self.get_distributed_ledgers()
|
|
self.add(network, block_chains)
|
|
self.play(self.alice.change, "conniving")
|
|
self.wait()
|
|
|
|
def show_fraudulent_block(self):
|
|
block = self.get_fraud_block()
|
|
block.next_to(self.alice, LEFT, LARGE_BUFF)
|
|
block.remove(block.content)
|
|
self.play(
|
|
ShowCreation(block),
|
|
Write(block.content),
|
|
self.alice.change, "raise_left_hand"
|
|
)
|
|
block.add(block.content)
|
|
self.wait()
|
|
|
|
self.block = block
|
|
|
|
def send_to_bob(self):
|
|
block = self.block.copy()
|
|
block.generate_target()
|
|
block.target.replace(
|
|
self.bob.block_chain.blocks[-1],
|
|
stretch = True
|
|
)
|
|
arrow = self.bob.block_chain.arrows[-1].copy()
|
|
VGroup(arrow, block.target).next_to(
|
|
self.bob.block_chain.blocks[-1], RIGHT, buff = 0
|
|
)
|
|
|
|
self.play(
|
|
MoveToTarget(block),
|
|
self.alice.change, "happy"
|
|
)
|
|
self.play(ShowCreation(arrow))
|
|
self.wait()
|
|
|
|
def dont_send_to_rest_of_network(self):
|
|
bubble = ThoughtBubble()
|
|
words = TextMobject("Alice", "never \\\\ paid", "Bob")
|
|
for name in "Alice", "Bob":
|
|
words.set_color_by_tex(name, self.get_color_from_name(name))
|
|
bubble.add_content(words)
|
|
bubble.resize_to_content()
|
|
bubble.add(*bubble.content)
|
|
bubble.move_to(self.you.get_corner(UP+RIGHT), DOWN+LEFT)
|
|
|
|
self.play(
|
|
self.charlie.change, "shruggie",
|
|
self.you.change, "shruggie",
|
|
)
|
|
self.play(OldLaggedStart(FadeIn, bubble))
|
|
self.play(self.bob.change, "confused", words)
|
|
self.wait(2)
|
|
|
|
###
|
|
|
|
def get_fraud_block(self):
|
|
block = self.get_block()
|
|
block.set_height(self.fraud_block_height)
|
|
content = VGroup()
|
|
|
|
tuples = [
|
|
("Prev hash", UP, BLUE),
|
|
("Proof of work", DOWN, GREEN),
|
|
]
|
|
for word, vect, color in tuples:
|
|
mob = TextMobject(word)
|
|
mob.set_color(color)
|
|
mob.set_height(0.07*block.get_height())
|
|
mob.next_to(
|
|
block.get_edge_center(vect), -vect,
|
|
buff = 0.06*block.get_height()
|
|
)
|
|
content.add(mob)
|
|
attr = word.lower().replace(" ", "_")
|
|
setattr(block, attr, mob)
|
|
|
|
payment = TextMobject("Alice", "pays", "Bob", "100 LD")
|
|
for name in "Alice", "Bob":
|
|
payment.set_color_by_tex(name, self.get_color_from_name(name))
|
|
payment.set_color_by_tex("LD", YELLOW)
|
|
payments = VGroup(
|
|
TexMobject("\\vdots"),
|
|
payment,
|
|
TexMobject("\\vdots")
|
|
)
|
|
payments.arrange(DOWN)
|
|
payments.set_width(0.9*block.get_width())
|
|
payments.move_to(block)
|
|
content.add(payments)
|
|
|
|
block.content = content
|
|
block.add(content)
|
|
return block
|
|
|
|
class AliceRacesOtherMiners(DoubleSpendingAttack):
|
|
CONFIG = {
|
|
"n_frames_per_pow" : 3,
|
|
"fraud_block_height" : 2,
|
|
"n_miners" : 3,
|
|
"n_proof_of_work_digits" : 11,
|
|
"proof_of_work_update_counter" : 0,
|
|
}
|
|
def construct(self):
|
|
self.initialize_characters()
|
|
self.find_proof_of_work()
|
|
self.receive_broadcast_from_other_miners()
|
|
self.race_between_alice_and_miners()
|
|
|
|
def initialize_characters(self):
|
|
alice = self.alice
|
|
bob = self.bob
|
|
alice_l = VGroup(alice, alice.label)
|
|
bob_l = VGroup(bob, bob.label)
|
|
bob_l.move_to(ORIGIN)
|
|
alice_l.to_corner(UP+LEFT)
|
|
alice_l.shift(DOWN)
|
|
self.add(bob_l, alice_l)
|
|
|
|
chain = self.get_block_chain()
|
|
chain.next_to(bob, UP)
|
|
self.block_chain = chain
|
|
self.add(chain)
|
|
|
|
fraud_block = self.get_fraud_block()
|
|
fraud_block.next_to(alice, RIGHT)
|
|
fraud_block.to_edge(UP)
|
|
proof_of_work = self.get_rand_int_mob()
|
|
proof_of_work.replace(fraud_block.proof_of_work, dim_to_match = 1)
|
|
fraud_block.content.remove(fraud_block.proof_of_work)
|
|
fraud_block.content.add(proof_of_work)
|
|
fraud_block.proof_of_work = proof_of_work
|
|
self.fraud_block = fraud_block
|
|
self.add(fraud_block)
|
|
|
|
miners = VGroup(*[
|
|
PiCreature(color = GREY)
|
|
for x in range(self.n_miners)
|
|
])
|
|
miners.set_height(alice.get_height())
|
|
miners.arrange(RIGHT, buff = LARGE_BUFF)
|
|
miners.to_edge(DOWN+LEFT)
|
|
miners.shift(0.5*UP)
|
|
miners_word = TextMobject("Miners")
|
|
miners_word.next_to(miners, DOWN)
|
|
self.miners = miners
|
|
self.add(miners_word, miners)
|
|
|
|
miner_blocks = VGroup()
|
|
self.proofs_of_work = VGroup()
|
|
self.add_foreground_mobject(self.proofs_of_work)
|
|
for miner in miners:
|
|
block = self.get_block()
|
|
block.set_width(1.5*miner.get_width())
|
|
block.next_to(miner, UP)
|
|
|
|
transactions = self.get_block_filler(block)
|
|
block.add(transactions)
|
|
|
|
proof_of_work = self.get_rand_int_mob()
|
|
prev_hash = TextMobject("Prev hash").set_color(BLUE)
|
|
for mob, vect in (proof_of_work, DOWN), (prev_hash, UP):
|
|
mob.set_height(0.1*block.get_height())
|
|
mob.next_to(
|
|
block.get_edge_center(vect), -vect,
|
|
buff = 0.05*block.get_height()
|
|
)
|
|
block.add(mob)
|
|
block.proof_of_work = proof_of_work
|
|
block.prev_hash = prev_hash
|
|
self.proofs_of_work.add(proof_of_work)
|
|
|
|
miner.block = block
|
|
miner_blocks.add(block)
|
|
self.add(miner_blocks)
|
|
|
|
def find_proof_of_work(self):
|
|
fraud_block = self.fraud_block
|
|
chain = self.block_chain
|
|
|
|
self.proofs_of_work.add(self.fraud_block.proof_of_work)
|
|
self.wait(3)
|
|
self.proofs_of_work.remove(self.fraud_block.proof_of_work)
|
|
fraud_block.proof_of_work.set_color(GREEN)
|
|
self.play(
|
|
Indicate(fraud_block.proof_of_work),
|
|
self.alice.change, "hooray"
|
|
)
|
|
self.wait()
|
|
|
|
block = fraud_block.copy()
|
|
block.generate_target()
|
|
block.target.replace(chain.blocks[-1], stretch = True)
|
|
arrow = chain.arrows[-1].copy()
|
|
VGroup(arrow, block.target).next_to(
|
|
chain.blocks[-1], RIGHT, buff = 0
|
|
)
|
|
self.remove(fraud_block)
|
|
self.clean_fraud_block_content()
|
|
self.fraud_fork_head = block
|
|
self.fraud_fork_arrow = arrow
|
|
|
|
self.play(MoveToTarget(block))
|
|
self.play(ShowCreation(arrow))
|
|
self.play(self.alice.change, "happy")
|
|
self.wait()
|
|
|
|
def receive_broadcast_from_other_miners(self):
|
|
winner = self.miners[-1]
|
|
proof_of_work = winner.block.proof_of_work
|
|
self.proofs_of_work.remove(proof_of_work)
|
|
proof_of_work.set_color(GREEN)
|
|
self.play(
|
|
Indicate(proof_of_work),
|
|
winner.change, "hooray"
|
|
)
|
|
block = winner.block.copy()
|
|
proof_of_work.set_color(WHITE)
|
|
self.remove(winner.block)
|
|
|
|
ff_head = self.fraud_fork_head
|
|
ff_arrow = self.fraud_fork_arrow
|
|
arrow = ff_arrow.copy()
|
|
movers = [ff_head, ff_arrow, block, arrow]
|
|
for mover in movers:
|
|
mover.generate_target()
|
|
block.target.replace(ff_head, stretch = True)
|
|
|
|
dist = 0.5*ff_head.get_height() + MED_SMALL_BUFF
|
|
block.target.shift(dist*DOWN)
|
|
ff_head.target.shift(dist*UP)
|
|
arrow.target[1].shift(dist*DOWN)
|
|
arrow.target.points[-2:] += dist*DOWN
|
|
ff_arrow.target[1].shift(dist*UP)
|
|
ff_arrow.target.points[-2:] += dist*UP
|
|
|
|
self.play(
|
|
Broadcast(block),
|
|
*[
|
|
MoveToTarget(
|
|
mover, run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.8)
|
|
)
|
|
for mover in movers
|
|
]
|
|
)
|
|
self.play(
|
|
FadeIn(winner.block),
|
|
winner.change_mode, "plain",
|
|
self.alice.change, "sassy",
|
|
)
|
|
self.proofs_of_work.add(winner.block.proof_of_work)
|
|
self.wait(2)
|
|
self.play(
|
|
self.alice.change, "pondering",
|
|
FadeIn(self.fraud_block)
|
|
)
|
|
self.proofs_of_work.add(self.fraud_block.proof_of_work)
|
|
|
|
self.valid_fork_head = block
|
|
|
|
def race_between_alice_and_miners(self):
|
|
last_fraud_block = self.fraud_fork_head
|
|
last_valid_block = self.valid_fork_head
|
|
chain = self.block_chain
|
|
winners = [
|
|
"Alice", "Alice",
|
|
"Miners", "Miners", "Miners",
|
|
"Alice", "Miners", "Miners", "Miners",
|
|
"Alice", "Miners", "Miners"
|
|
]
|
|
|
|
self.revert_to_original_skipping_status()
|
|
for winner in winners:
|
|
self.wait()
|
|
if winner == "Alice":
|
|
block = self.fraud_block
|
|
prev_block = last_fraud_block
|
|
else:
|
|
block = random.choice(self.miners).block
|
|
prev_block = last_valid_block
|
|
block_copy = block.deepcopy()
|
|
block_copy.proof_of_work.set_color(GREEN)
|
|
block_copy.generate_target()
|
|
block_copy.target.replace(chain.blocks[1], stretch = True)
|
|
arrow = chain.arrows[0].copy()
|
|
VGroup(arrow, block_copy.target).next_to(
|
|
prev_block, RIGHT, buff = 0
|
|
)
|
|
anims = [
|
|
MoveToTarget(block_copy, run_time = 2),
|
|
ShowCreation(
|
|
arrow,
|
|
run_time = 2,
|
|
rate_func = squish_rate_func(smooth, 0.5, 1),
|
|
),
|
|
FadeIn(block),
|
|
]
|
|
if winner == "Alice":
|
|
last_fraud_block = block_copy
|
|
else:
|
|
last_valid_block = block_copy
|
|
anims.append(Broadcast(block, run_time = 2))
|
|
self.proofs_of_work.remove(block.proof_of_work)
|
|
self.play(*anims)
|
|
self.proofs_of_work.add(block.proof_of_work)
|
|
|
|
|
|
#####
|
|
|
|
def wait(self, time = 1):
|
|
self.play(
|
|
Animation(VGroup(*self.foreground_mobjects)),
|
|
run_time = time
|
|
)
|
|
|
|
def update_frame(self, *args, **kwargs):
|
|
self.update_proofs_of_work()
|
|
Scene.update_frame(self, *args, **kwargs)
|
|
|
|
def update_proofs_of_work(self):
|
|
self.proof_of_work_update_counter += 1
|
|
if self.proof_of_work_update_counter%self.n_frames_per_pow != 0:
|
|
return
|
|
for proof_of_work in self.proofs_of_work:
|
|
new_pow = self.get_rand_int_mob()
|
|
new_pow.replace(proof_of_work, dim_to_match = 1)
|
|
Transform(proof_of_work, new_pow).update(1)
|
|
|
|
def get_rand_int_mob(self):
|
|
e = self.n_proof_of_work_digits
|
|
return Integer(random.randint(10**e, 10**(e+1)))
|
|
|
|
def clean_fraud_block_content(self):
|
|
content = self.fraud_block.content
|
|
payments = content[1]
|
|
content.remove(payments)
|
|
transactions = self.get_block_filler(self.fraud_block)
|
|
content.add(transactions)
|
|
|
|
def get_block_filler(self, block):
|
|
result = TextMobject("$\\langle$Transactions$\\rangle$")
|
|
result.set_width(0.8*block.get_width())
|
|
result.move_to(block)
|
|
return result
|
|
|
|
class WhenToTrustANewBlock(DistributedBlockChainScene):
|
|
def construct(self):
|
|
chain = self.block_chain = self.get_block_chain()
|
|
chain.scale(2)
|
|
chain.to_edge(LEFT)
|
|
self.add(chain)
|
|
|
|
words = list(map(TextMobject, [
|
|
"Don't trust yet",
|
|
"Still don't trust",
|
|
"...a little more...",
|
|
"Maybe trust",
|
|
"Probably safe",
|
|
"Alright, you're good."
|
|
]))
|
|
colors = [RED, RED, YELLOW, YELLOW, GREEN, GREEN]
|
|
self.add_new_block()
|
|
arrow = Arrow(UP, DOWN, color = RED)
|
|
arrow.next_to(chain.blocks[-1], UP)
|
|
for word, color in zip(words, colors):
|
|
word.set_color(color)
|
|
word.next_to(arrow, UP)
|
|
word = words[0]
|
|
self.play(
|
|
FadeIn(word),
|
|
ShowCreation(arrow)
|
|
)
|
|
for new_word in words[1:]:
|
|
kwargs = {
|
|
"run_time" : 3,
|
|
"rate_func" : squish_rate_func(smooth, 0.7, 1)
|
|
}
|
|
self.add_new_block(
|
|
Transform(word, new_word, **kwargs),
|
|
ApplyMethod(
|
|
arrow.set_color, new_word.get_color(),
|
|
**kwargs
|
|
)
|
|
)
|
|
self.wait(2)
|
|
|
|
def get_block(self):
|
|
block = DistributedBlockChainScene.get_block(self)
|
|
tuples = [
|
|
("Prev hash", UP, BLUE),
|
|
("Proof of work", DOWN, GREEN),
|
|
]
|
|
for word, vect, color in tuples:
|
|
mob = TextMobject(word)
|
|
mob.set_color(color)
|
|
mob.set_height(0.07*block.get_height())
|
|
mob.next_to(
|
|
block.get_edge_center(vect), -vect,
|
|
buff = 0.06*block.get_height()
|
|
)
|
|
block.add(mob)
|
|
attr = word.lower().replace(" ", "_")
|
|
setattr(block, attr, mob)
|
|
transactions = TextMobject("$\\langle$Transactions$\\rangle$")
|
|
transactions.set_width(0.8*block.get_width())
|
|
transactions.move_to(block)
|
|
block.add(transactions)
|
|
return block
|
|
|
|
def add_new_block(self, *added_anims):
|
|
blocks = self.block_chain.blocks
|
|
arrows = self.block_chain.arrows
|
|
block = blocks[-1].copy()
|
|
arrow = arrows[-1].copy()
|
|
VGroup(block, arrow).next_to(blocks[-1], RIGHT, buff = 0)
|
|
corner = FRAME_X_RADIUS*RIGHT + FRAME_Y_RADIUS*UP
|
|
block.save_state()
|
|
block.next_to(corner, UP+RIGHT)
|
|
|
|
self.play(
|
|
Broadcast(block),
|
|
ApplyMethod(
|
|
block.restore,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.3, 0.8),
|
|
),
|
|
ShowCreation(
|
|
arrow,
|
|
run_time = 3,
|
|
rate_func = squish_rate_func(smooth, 0.7, 1),
|
|
),
|
|
*added_anims
|
|
)
|
|
arrows.add(arrow)
|
|
blocks.add(block)
|
|
|
|
class MainIdeas(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Main ideas")
|
|
title.scale(1.5)
|
|
h_line = Line(LEFT, RIGHT)
|
|
h_line.set_width(FRAME_X_RADIUS)
|
|
h_line.next_to(title, DOWN)
|
|
VGroup(title, h_line).to_corner(UP+LEFT)
|
|
|
|
ideas = VGroup(*[
|
|
TextMobject("$\\cdot$ " + words)
|
|
for words in [
|
|
"Digital signatures",
|
|
"The ledger is the currency",
|
|
"Decentralize",
|
|
"Proof of work",
|
|
"Block chain",
|
|
]
|
|
])
|
|
colors = BLUE, WHITE, RED, GREEN, YELLOW
|
|
for idea, color in zip(ideas, colors):
|
|
idea.set_color(color)
|
|
ideas.arrange(
|
|
DOWN,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
ideas.next_to(h_line, DOWN)
|
|
|
|
self.add(title, h_line)
|
|
for idea in ideas:
|
|
self.play(OldLaggedStart(FadeIn, idea))
|
|
self.wait()
|
|
|
|
class VariableProofOfWork(WhenToTrustANewBlock):
|
|
CONFIG = {
|
|
"block_height" : 3,
|
|
"block_width" : 3,
|
|
"n_guesses" : 60,
|
|
"n_proof_of_work_digits" : 11,
|
|
"n_miners" : 6,
|
|
}
|
|
def construct(self):
|
|
self.add_miner_and_hash()
|
|
self.change_requirement()
|
|
self.add_more_miners()
|
|
|
|
def add_miner_and_hash(self):
|
|
miner = PiCreature(color = GREY)
|
|
miner.scale(0.7)
|
|
miner.to_edge(LEFT)
|
|
|
|
block = self.get_block()
|
|
block.next_to(miner, RIGHT)
|
|
old_proof_of_work = block.proof_of_work
|
|
proof_of_work = self.get_rand_int_mob()
|
|
proof_of_work.replace(old_proof_of_work, dim_to_match = 1)
|
|
block.remove(old_proof_of_work)
|
|
block.add(proof_of_work)
|
|
block.proof_of_work = proof_of_work
|
|
|
|
arrow = Arrow(LEFT, RIGHT)
|
|
arrow.next_to(block)
|
|
sha_tex = TextMobject("SHA256")
|
|
sha_tex.scale(0.7)
|
|
sha_tex.next_to(arrow, UP, SMALL_BUFF)
|
|
sha_tex.set_color(YELLOW)
|
|
arrow.add(sha_tex)
|
|
|
|
digest = sha256_tex_mob("Random")
|
|
digest.next_to(arrow.get_end(), RIGHT)
|
|
|
|
miner.change("pondering", digest)
|
|
|
|
self.add(miner, block, arrow, digest)
|
|
for x in range(self.n_guesses):
|
|
new_pow = self.get_rand_int_mob()
|
|
new_pow.replace(proof_of_work, dim_to_match = 1)
|
|
Transform(proof_of_work, new_pow).update(1)
|
|
if x == self.n_guesses-1:
|
|
n_zeros = 60
|
|
else:
|
|
n_zeros = 0
|
|
new_digest = sha256_tex_mob(str(x+1), n_zeros)
|
|
new_digest.replace(digest)
|
|
Transform(digest, new_digest).update(1)
|
|
self.wait(1./20)
|
|
proof_of_work.set_color(GREEN)
|
|
VGroup(*digest[:60]).set_color(YELLOW)
|
|
|
|
self.miner = miner
|
|
self.block = block
|
|
self.arrow = arrow
|
|
self.digest = digest
|
|
|
|
def change_requirement(self):
|
|
digest = self.digest
|
|
requirement = TextMobject(
|
|
"Must start with \\\\",
|
|
"60", "zeros"
|
|
)
|
|
requirement.next_to(digest, UP, MED_LARGE_BUFF)
|
|
self.n_zeros_mob = requirement.get_part_by_tex("60")
|
|
self.n_zeros_mob.set_color(YELLOW)
|
|
|
|
self.play(Write(requirement, run_time = 2))
|
|
self.wait(2)
|
|
for n_zeros in 30, 32, 35, 37, 42:
|
|
self.change_challenge(n_zeros)
|
|
self.wait()
|
|
|
|
def add_more_miners(self):
|
|
miner = self.miner
|
|
block = self.block
|
|
miner_block = VGroup(miner, block)
|
|
target = miner_block.copy()
|
|
target[1].scale(
|
|
0.5, about_point = miner.get_right()
|
|
)
|
|
copies = VGroup(*[
|
|
target.copy()
|
|
for x in range(self.n_miners - 1)
|
|
])
|
|
everyone = VGroup(target, *copies)
|
|
everyone.arrange(DOWN)
|
|
everyone.set_height(FRAME_HEIGHT - LARGE_BUFF)
|
|
everyone.to_corner(UP+LEFT)
|
|
|
|
self.play(Transform(miner_block, target))
|
|
self.play(OldLaggedStart(FadeIn, copies))
|
|
self.change_challenge(72)
|
|
self.wait(2)
|
|
|
|
###
|
|
def change_challenge(self, n_zeros):
|
|
digest = self.digest
|
|
proof_of_work = self.block.proof_of_work
|
|
n_zeros_mob = self.n_zeros_mob
|
|
|
|
new_digest = sha256_tex_mob(str(n_zeros), n_zeros)
|
|
new_digest.move_to(digest)
|
|
VGroup(*new_digest[:n_zeros]).set_color(YELLOW)
|
|
new_n_zeros_mob = TexMobject(str(n_zeros))
|
|
new_n_zeros_mob.move_to(n_zeros_mob)
|
|
new_n_zeros_mob.set_color(n_zeros_mob.get_color())
|
|
new_pow = self.get_rand_int_mob()
|
|
new_pow.replace(proof_of_work, dim_to_match = 1)
|
|
new_pow.set_color(proof_of_work.get_color())
|
|
|
|
self.play(
|
|
Transform(n_zeros_mob, new_n_zeros_mob),
|
|
Transform(
|
|
digest, new_digest,
|
|
lag_ratio = 0.5
|
|
),
|
|
Transform(proof_of_work, new_pow),
|
|
)
|
|
|
|
def get_rand_int_mob(self):
|
|
e = self.n_proof_of_work_digits
|
|
return Integer(random.randint(10**e, 10**(e+1)))
|
|
|
|
class CompareBlockTimes(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Average block time")
|
|
title.scale(1.5)
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT)
|
|
h_line.set_width(FRAME_X_RADIUS)
|
|
h_line.next_to(title, DOWN, SMALL_BUFF)
|
|
|
|
examples = VGroup(
|
|
TextMobject("BTC: ", "10 minutes"),
|
|
TextMobject("ETH: ", "15 Seconds"),
|
|
TextMobject("XRP: ", "3.5 Seconds"),
|
|
TextMobject("LTC: ", "2.5 Minutes"),
|
|
)
|
|
examples.arrange(
|
|
DOWN,
|
|
buff = LARGE_BUFF,
|
|
aligned_edge = LEFT,
|
|
)
|
|
examples.next_to(h_line, DOWN)
|
|
logos = VGroup(
|
|
BitcoinLogo(),
|
|
EthereumLogo(),
|
|
ImageMobject("ripple_logo"),
|
|
LitecoinLogo(),
|
|
)
|
|
colors = [BITCOIN_COLOR, GREEN, BLUE_B, LIGHT_GREY]
|
|
for logo, example, color in zip(logos, examples, colors):
|
|
logo.set_height(0.5)
|
|
logo.next_to(example, LEFT)
|
|
example[0].set_color(color)
|
|
|
|
self.add(title, h_line)
|
|
self.play(
|
|
FadeIn(examples[0]),
|
|
DrawBorderThenFill(logos[0])
|
|
)
|
|
self.wait()
|
|
self.play(*[
|
|
OldLaggedStart(FadeIn, VGroup(*group[1:]))
|
|
for group in (examples, logos)
|
|
])
|
|
self.wait(2)
|
|
|
|
# def get_ethereum_logo(self):
|
|
# logo = SVGMobject(
|
|
# file_name = "ethereum_logo",
|
|
# height = 1,
|
|
# )
|
|
# logo.set_fill(GREEN, 1)
|
|
# logo.set_stroke(WHITE, 3)
|
|
# logo.set_color_by_gradient(GREEN_B, GREEN_D)
|
|
# logo.set_width(1)
|
|
# logo.center()
|
|
# self.add(SurroundingRectangle(logo))
|
|
# return logo
|
|
|
|
class BlockRewards(Scene):
|
|
def construct(self):
|
|
title = TextMobject("Block rewards")
|
|
title.scale(1.5)
|
|
logo = BitcoinLogo()
|
|
logo.set_height(0.75)
|
|
logo.next_to(title, LEFT)
|
|
title.add(logo)
|
|
title.to_edge(UP)
|
|
h_line = Line(LEFT, RIGHT)
|
|
h_line.set_width(FRAME_X_RADIUS)
|
|
h_line.next_to(title, DOWN)
|
|
self.add(title, logo, h_line)
|
|
|
|
rewards = VGroup(
|
|
TextMobject("Jan 2009 - Nov 2012:", "50", "BTC"),
|
|
TextMobject("Nov 2012 - Jul 2016:", "25", "BTC"),
|
|
TextMobject("Jul 2016 - Feb 2020$^*$:", "12.5", "BTC"),
|
|
TextMobject("Feb 2020$^*$ - Sep 2023$^*$:", "6.25", "BTC"),
|
|
)
|
|
rewards.arrange(
|
|
DOWN,
|
|
buff = MED_LARGE_BUFF,
|
|
aligned_edge = LEFT
|
|
)
|
|
rewards.next_to(h_line, DOWN)
|
|
for reward in rewards:
|
|
reward[1].set_color(YELLOW)
|
|
|
|
footnote = TextMobject(
|
|
"$^*$ Extrapolating from the 25 BTC reward period"
|
|
)
|
|
footnote.scale(0.5)
|
|
footnote.to_corner(DOWN+RIGHT)
|
|
|
|
self.play(OldLaggedStart(
|
|
FadeIn, rewards,
|
|
run_time = 4,
|
|
lag_ratio = 0.5
|
|
))
|
|
self.play(FadeIn(footnote))
|
|
self.wait(3)
|
|
|
|
class ShowFirstFewBlocks(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class ShowGeometricSum(Scene):
|
|
def construct(self):
|
|
equation = TexMobject(
|
|
"210{,}000", "(",
|
|
"50", "+", "25", "+", "12.5", "+",
|
|
"6.25", "+\\cdots", ")", "=", "21{,}000{,}000"
|
|
)
|
|
numbers = ["50", "25", "12.5", "6.25"]
|
|
colors = color_gradient([BLUE_D, BLUE_B], 4)
|
|
for tex, color in zip(numbers, colors):
|
|
equation.set_color_by_tex(tex, color)
|
|
equation[-1].set_color(YELLOW)
|
|
|
|
self.add(*equation[:2] + equation[-3:-1])
|
|
for i in range(2, 9, 2):
|
|
self.play(FadeIn(VGroup(*equation[i:i+2])))
|
|
self.wait()
|
|
self.play(Write(equation[-1]))
|
|
self.wait(2)
|
|
|
|
class TransactionFeeExample(PiCreatureScene):
|
|
def construct(self):
|
|
alice = self.pi_creature
|
|
payment = TextMobject(
|
|
"Alice", "pays", "Bob", "0.42 BTC",
|
|
)
|
|
payment.set_color_by_tex("Alice", BLUE_C)
|
|
payment.set_color_by_tex("Bob", MAROON)
|
|
payment.set_color_by_tex("BTC", YELLOW)
|
|
payment.move_to(2.5*UP)
|
|
|
|
fee = TextMobject("And leaves", "0.001 BTC", "to the miner")
|
|
fee.set_color_by_tex("BTC", YELLOW)
|
|
fee.next_to(payment, DOWN)
|
|
|
|
signature = TextMobject(
|
|
"$\\langle$Alice's digital signature$\\rangle$"
|
|
)
|
|
signature.set_color(BLUE_C)
|
|
signature.next_to(fee, DOWN)
|
|
|
|
group = VGroup(payment, fee, signature)
|
|
rect = SurroundingRectangle(group, color = BLUE_B)
|
|
|
|
incentive_words = TextMobject(
|
|
"Incentivizes miner \\\\ to include"
|
|
)
|
|
incentive_words.next_to(rect, DOWN, buff = 1.5)
|
|
incentive_words.shift(2*RIGHT)
|
|
arrow = Arrow(
|
|
incentive_words.get_top(),
|
|
rect.get_bottom(),
|
|
buff = MED_LARGE_BUFF
|
|
)
|
|
|
|
fee.save_state()
|
|
fee.shift(DOWN)
|
|
fee.set_fill(opacity = 0)
|
|
|
|
self.play(Write(payment))
|
|
self.wait()
|
|
self.play(
|
|
alice.change, "raise_right_hand", payment,
|
|
fee.restore,
|
|
)
|
|
self.play(Write(signature))
|
|
self.play(
|
|
ShowCreation(rect),
|
|
alice.change_mode, "happy"
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
Write(incentive_words),
|
|
ShowCreation(arrow),
|
|
alice.change, "pondering"
|
|
)
|
|
self.wait(2)
|
|
|
|
def create_pi_creature(self):
|
|
alice = PiCreature(color = BLUE_C)
|
|
alice.to_edge(DOWN)
|
|
alice.shift(FRAME_X_RADIUS*LEFT/2)
|
|
return alice
|
|
|
|
class ShowBitcoinBlockSize(LedgerScene):
|
|
CONFIG = {
|
|
"denomination" : "BTC"
|
|
}
|
|
def construct(self):
|
|
block = VGroup()
|
|
ledger = self.get_ledger()
|
|
|
|
payments = VGroup(*[
|
|
self.add_payment_line_to_ledger(*args)
|
|
for args in [
|
|
("Alice", "Bob", "0.42"),
|
|
("You", "Charlie", "3.14"),
|
|
("Bob", "You", "2.72"),
|
|
("Alice", "Charlie", "4.67"),
|
|
]
|
|
])
|
|
dots = TexMobject("\\vdots")
|
|
dots.next_to(payments, DOWN)
|
|
payments.add(dots)
|
|
payments.to_edge(LEFT)
|
|
payments.shift(DOWN+0.5*RIGHT)
|
|
payments_rect = SurroundingRectangle(
|
|
payments, color = WHITE, buff = MED_LARGE_BUFF
|
|
)
|
|
block.add(payments_rect, payments)
|
|
|
|
tuples = [
|
|
("Prev hash", UP, BLUE_C),
|
|
("Proof of work", DOWN, GREEN),
|
|
]
|
|
for word, vect, color in tuples:
|
|
mob = TextMobject(word)
|
|
mob.set_color(color)
|
|
rect = SurroundingRectangle(
|
|
mob, color = WHITE, buff = MED_SMALL_BUFF
|
|
)
|
|
VGroup(mob, rect).next_to(payments_rect, vect, 0)
|
|
rect.stretch_to_fit_width(payments_rect.get_width())
|
|
block.add(mob, rect)
|
|
|
|
title = VGroup(
|
|
BitcoinLogo(height = 0.75),
|
|
TextMobject("Block").scale(1.5)
|
|
)
|
|
title.arrange(RIGHT, SMALL_BUFF)
|
|
title.next_to(block, UP)
|
|
|
|
brace = Brace(payments_rect, RIGHT)
|
|
limit = brace.get_text(
|
|
"Limited to\\\\",
|
|
"$\\sim 2{,}400$", "transactions"
|
|
)
|
|
limit.set_color_by_tex("2{,}400", RED)
|
|
|
|
self.add(title, block)
|
|
self.remove(payments)
|
|
self.play(
|
|
GrowFromCenter(brace),
|
|
Write(limit)
|
|
)
|
|
self.play(OldLaggedStart(FadeIn, payments))
|
|
self.wait()
|
|
|
|
####Visa
|
|
|
|
visa_logo = SVGMobject(
|
|
file_name = "visa_logo",
|
|
height = 0.5,
|
|
stroke_width = 0,
|
|
fill_color = BLUE_D,
|
|
fill_opacity = 1,
|
|
)
|
|
visa_logo[-1].set_color("#faa61a")
|
|
visa_logo.sort()
|
|
avg_rate = TextMobject("Avg: $1{,}700$/second")
|
|
max_rate = TextMobject("Max: $>24{,}000$/second")
|
|
rates = VGroup(avg_rate, max_rate)
|
|
rates.scale(0.8)
|
|
rates.arrange(DOWN, aligned_edge = LEFT)
|
|
rates.next_to(visa_logo, RIGHT, buff = MED_SMALL_BUFF)
|
|
visa = VGroup(visa_logo, rates)
|
|
visa.to_corner(UP+RIGHT)
|
|
|
|
self.play(OldLaggedStart(DrawBorderThenFill, visa_logo))
|
|
self.play(OldLaggedStart(FadeIn, avg_rate))
|
|
self.wait()
|
|
self.play(OldLaggedStart(FadeIn, max_rate))
|
|
self.wait(2)
|
|
|
|
class CurrentAverageFees(Scene):
|
|
def construct(self):
|
|
fees = TextMobject(
|
|
"Current average fees: ",
|
|
"$\\sim 0.0013$ BTC",
|
|
"$\\approx$", "\\$3.39"
|
|
)
|
|
fees.set_color_by_tex("BTC", YELLOW)
|
|
fees.set_color_by_tex("\\$", GREEN)
|
|
fees.to_edge(UP)
|
|
|
|
self.play(Write(fees))
|
|
self.wait()
|
|
|
|
class HighlightingAFewFees(ExternallyAnimatedScene):
|
|
pass
|
|
|
|
class TopicsNotCovered(TeacherStudentsScene):
|
|
def construct(self):
|
|
title = TextMobject("Topics not covered:")
|
|
title.to_corner(UP+LEFT)
|
|
title.set_color(YELLOW)
|
|
title.save_state()
|
|
title.shift(DOWN)
|
|
title.set_fill(opacity = 0)
|
|
|
|
topics = VGroup(*list(map(TextMobject, [
|
|
"Merkle trees",
|
|
"Alternatives to proof of work",
|
|
"Scripting",
|
|
"$\\vdots$",
|
|
"(See links in description)",
|
|
])))
|
|
topics.arrange(DOWN, aligned_edge = LEFT)
|
|
topics[-2].next_to(topics[-3], DOWN)
|
|
topics.next_to(title, RIGHT)
|
|
topics.to_edge(UP)
|
|
|
|
self.play(
|
|
title.restore,
|
|
self.teacher.change_mode, "raise_right_hand"
|
|
)
|
|
for topic in topics:
|
|
self.change_student_modes(
|
|
"confused", "thinking","pondering",
|
|
look_at_arg = topic,
|
|
added_anims = [OldLaggedStart(FadeIn, topic)]
|
|
)
|
|
self.wait()
|
|
|
|
class Exchange(Animation):
|
|
CONFIG = {
|
|
"rate_func" : None,
|
|
}
|
|
def __init__(self, exchange, **kwargs):
|
|
self.swap = Swap(
|
|
exchange.left,
|
|
exchange.right,
|
|
)
|
|
self.changed_symbols_yet = False
|
|
Animation.__init__(self, exchange, **kwargs)
|
|
|
|
def interpolate_mobject(self, alpha):
|
|
exchange = self.mobject
|
|
if alpha < 1./3:
|
|
self.swap.update(3*alpha)
|
|
elif alpha < 2./3:
|
|
sub_alpha = alpha*3 - 1
|
|
group = VGroup(exchange.left, exchange.right)
|
|
group.set_fill(opacity = 1-smooth(sub_alpha))
|
|
else:
|
|
if not self.changed_symbols_yet:
|
|
new_left = random.choice(
|
|
exchange.cryptocurrencies
|
|
).copy()
|
|
new_left.move_to(exchange.right)
|
|
new_right = random.choice(
|
|
exchange.currencies
|
|
).copy()
|
|
new_right.move_to(exchange.left)
|
|
Transform(exchange.left, new_left).update(1)
|
|
Transform(exchange.right, new_right).update(1)
|
|
self.changed_symbols_yet = True
|
|
sub_alpha = 3*alpha - 2
|
|
group = VGroup(exchange.left, exchange.right)
|
|
group.set_fill(opacity = smooth(sub_alpha))
|
|
|
|
class ShowManyExchanges(Scene):
|
|
CONFIG = {
|
|
"n_rows" : 2,
|
|
"n_cols" : 8,
|
|
"shift_radius" : 0.5,
|
|
"run_time" : 30,
|
|
}
|
|
def construct(self):
|
|
cryptocurrencies = self.get_cryptocurrencies()
|
|
currencies = self.get_currencies()
|
|
for currency in it.chain(currencies, cryptocurrencies):
|
|
currency.set_height(0.5)
|
|
currency.align_data(EthereumLogo())
|
|
exchange = VGroup(*[
|
|
Arrow(
|
|
p1, p2,
|
|
path_arc = np.pi,
|
|
buff = MED_LARGE_BUFF
|
|
)
|
|
for p1, p2 in [(LEFT, RIGHT), (RIGHT, LEFT)]
|
|
]).set_color(WHITE)
|
|
exchanges = VGroup(*[
|
|
VGroup(*[
|
|
exchange.copy()
|
|
for x in range(3)
|
|
]).arrange(RIGHT, buff = 2*LARGE_BUFF)
|
|
for y in range(3)
|
|
]).arrange(DOWN, buff = MED_LARGE_BUFF)
|
|
exchanges = VGroup(*it.chain(*exchanges))
|
|
self.add(exchanges)
|
|
start_times = list(np.linspace(0, 2, len(exchanges)))
|
|
random.shuffle(start_times)
|
|
for exchange, start_time in zip(exchanges, start_times):
|
|
left = random.choice(cryptocurrencies).copy()
|
|
right = random.choice(currencies).copy()
|
|
left.move_to(exchange.get_left())
|
|
right.move_to(exchange.get_right())
|
|
|
|
exchange.left = left
|
|
exchange.right = right
|
|
exchange.start_time = start_time
|
|
exchange.add(left, right)
|
|
exchange.currencies = currencies
|
|
exchange.cryptocurrencies = cryptocurrencies
|
|
exchange.animation = Exchange(exchange)
|
|
|
|
times = np.arange(0, self.run_time, self.frame_duration)
|
|
from scene.scene import ProgressDisplay
|
|
for t in ProgressDisplay(times):
|
|
for exchange in exchanges:
|
|
sub_t = t - exchange.start_time
|
|
if sub_t < 0:
|
|
continue
|
|
elif sub_t > 3:
|
|
exchange.start_time = t
|
|
sub_t = 0
|
|
exchange.animation = Exchange(exchange)
|
|
exchange.animation.update(sub_t/3.0)
|
|
self.update_frame(
|
|
self.extract_mobject_family_members(exchanges)
|
|
)
|
|
self.add_frames(self.get_frame())
|
|
|
|
def get_cryptocurrencies(self):
|
|
return [
|
|
BitcoinLogo(),
|
|
BitcoinLogo(),
|
|
BitcoinLogo(),
|
|
EthereumLogo(),
|
|
LitecoinLogo()
|
|
]
|
|
|
|
def get_currencies(self):
|
|
return [
|
|
TexMobject("\\$").set_color(GREEN),
|
|
TexMobject("\\$").set_color(GREEN),
|
|
TexMobject("\\$").set_color(GREEN),
|
|
SVGMobject(
|
|
file_name = "euro_symbol",
|
|
stroke_width = 0,
|
|
fill_opacity = 1,
|
|
fill_color = BLUE,
|
|
),
|
|
SVGMobject(
|
|
file_name = "yen_symbol",
|
|
stroke_width = 0,
|
|
fill_opacity = 1,
|
|
fill_color = RED,
|
|
)
|
|
]
|
|
|
|
class ShowLDAndOtherCurrencyExchanges(ShowManyExchanges):
|
|
CONFIG = {
|
|
"run_time" : 15,
|
|
}
|
|
def get_cryptocurrencies(self):
|
|
euro = SVGMobject(
|
|
file_name = "euro_symbol",
|
|
stroke_width = 0,
|
|
fill_opacity = 1,
|
|
fill_color = BLUE,
|
|
)
|
|
return [
|
|
TextMobject("LD").set_color(YELLOW),
|
|
TextMobject("LD").set_color(YELLOW),
|
|
euro, euro.copy(),
|
|
SVGMobject(
|
|
file_name = "yen_symbol",
|
|
stroke_width = 0,
|
|
fill_opacity = 1,
|
|
fill_color = RED,
|
|
),
|
|
BitcoinLogo(),
|
|
]
|
|
|
|
def get_currencies(self):
|
|
return [TexMobject("\\$").set_color(GREEN)]
|
|
|
|
class CryptoPatreonThanks(PatreonThanks):
|
|
CONFIG = {
|
|
"specific_patrons" : [
|
|
"Ali Yahya",
|
|
"Desmos",
|
|
"Burt Humburg",
|
|
"CrypticSwarm",
|
|
"Juan Benet",
|
|
"Samantha D. Suplee",
|
|
"James Park",
|
|
"Erik Sundell",
|
|
"Yana Chernobilsky",
|
|
"Kaustuv DeBiswas",
|
|
"Kathryn Schmiedicke",
|
|
"Karan Bhargava",
|
|
"Yu Jun",
|
|
"Dave Nicponski",
|
|
"Damion Kistler",
|
|
"Markus Persson",
|
|
"Yoni Nazarathy",
|
|
"Ed Kellett",
|
|
"Joseph John Cox",
|
|
"Dan Buchoff",
|
|
"Luc Ritchie",
|
|
"Andrew Busey",
|
|
"Michael McGuffin",
|
|
"John Haley",
|
|
"Mourits de Beer",
|
|
"Ankalagon",
|
|
"Eric Lavault",
|
|
"Tomohiro Furusawa",
|
|
"Boris Veselinovich",
|
|
"Julian Pulgarin",
|
|
"Jeff Linse",
|
|
"Cooper Jones",
|
|
"Ryan Dahl",
|
|
"Mark Govea",
|
|
"Robert Teed",
|
|
"Jason Hise",
|
|
"Meshal Alshammari",
|
|
"Bernd Sing",
|
|
"Nils Schneider",
|
|
"James Thornton",
|
|
"Mustafa Mahdi",
|
|
"Mathew Bramson",
|
|
"Jerry Ling",
|
|
"Vecht",
|
|
"Shimin Kuang",
|
|
"Rish Kundalia",
|
|
"Achille Brighton",
|
|
"Ripta Pasay",
|
|
]
|
|
}
|
|
|
|
class ProtocolLabs(PiCreatureScene):
|
|
def construct(self):
|
|
morty = self.pi_creature
|
|
logo = self.get_logo()
|
|
logo.next_to(morty, UP)
|
|
logo.shift_onto_screen()
|
|
screen_rect = ScreenRectangle()
|
|
screen_rect.set_height(5)
|
|
screen_rect.to_edge(LEFT)
|
|
|
|
self.play(
|
|
DrawBorderThenFill(logo[0]),
|
|
OldLaggedStart(FadeIn, logo[1]),
|
|
morty.change, "raise_right_hand",
|
|
)
|
|
self.wait()
|
|
self.play(
|
|
logo.scale, 0.5,
|
|
logo.to_corner, UP+LEFT,
|
|
ShowCreation(screen_rect)
|
|
)
|
|
modes = ["pondering", "happy", "happy"]
|
|
for mode in modes:
|
|
for x in range(2):
|
|
self.play(Blink(morty))
|
|
self.wait(3)
|
|
self.play(morty.change, mode, screen_rect)
|
|
|
|
def get_logo(self):
|
|
logo = SVGMobject(
|
|
file_name = "protocol_labs_logo",
|
|
height = 1.5,
|
|
fill_color = WHITE,
|
|
)
|
|
name = SVGMobject(
|
|
file_name = "protocol_labs_name",
|
|
height = 0.5*logo.get_height(),
|
|
fill_color = LIGHT_GREY,
|
|
)
|
|
for mob in logo, name:
|
|
for submob in mob:
|
|
submob.is_subpath = False
|
|
name.next_to(logo, RIGHT)
|
|
return VGroup(logo, name)
|
|
|
|
class Thumbnail(DistributedBlockChainScene):
|
|
CONFIG = {
|
|
"n_blocks" : 4,
|
|
}
|
|
def construct(self):
|
|
title = TextMobject("Crypto", "currencies", arg_separator = "")
|
|
title.scale(2.5)
|
|
title.to_edge(UP)
|
|
title[0].set_color(YELLOW)
|
|
title[0].set_stroke(RED, 2)
|
|
self.add(title)
|
|
|
|
# logos = VGroup(
|
|
# BitcoinLogo(), EthereumLogo(), LitecoinLogo()
|
|
# )
|
|
# for logo in logos:
|
|
# logo.set_height(1)
|
|
# logos.add(TexMobject("\\dots").scale(2))
|
|
# logos.arrange(RIGHT)
|
|
# logos.next_to(title, RIGHT, LARGE_BUFF)
|
|
# self.add(logos)
|
|
|
|
block_chain = self.get_block_chain()
|
|
block_chain.arrows.set_color(RED)
|
|
block_chain.blocks.set_color_by_gradient(BLUE, GREEN)
|
|
block_chain.set_width(FRAME_WIDTH-1)
|
|
block_chain.set_stroke(width = 12)
|
|
self.add(block_chain)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|