3b1b-manim/old_projects/crypto.py

5301 lines
163 KiB
Python

from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.compositions import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from topics.common_scenes import *
from scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject 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).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_submobjects(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.highlight("#" + 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.scale_to_fit_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.scale_to_fit_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.scale_to_fit_height(0.25*self.height)
ten.next_to(self.get_corner(vect), -vect, SMALL_BUFF)
ten.highlight(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.highlight_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(
LaggedStart(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_submobjects(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).highlight(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.highlight_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.scale_to_fit_width(FRAME_WIDTH - LARGE_BUFF)
topics.to_edge(UP)
topics.highlight_by_tex("Digital", BLUE)
topics.highlight_by_tex("Proof", GREEN)
topics.highlight_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:]).highlight(YELLOW)
parts.scale_to_fit_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().gradient_highlight(GREEN_B, GREEN_D),
)
for logo in logos:
logo.scale_to_fit_height(0.75)
logos.arrange_submobjects(DOWN, buff = MED_LARGE_BUFF)
logos.shift(LEFT)
logos.to_edge(UP)
names = 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(LaggedStart(FadeIn, dots, run_time = 1))
self.wait()
class Hype(TeacherStudentsScene):
def construct(self):
self.teacher.change_mode("guilty")
phrases = 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.scale_to_fit_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 = None
))
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.highlight(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.scale_to_fit_height(self.ledger_line_height)
if self.enumerate_lines:
num = TexMobject(str(len(items)) + ".")
num.scale(0.8)
num.highlight(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.highlight_by_tex(name.capitalize(), color)
if self.sign_transactions:
from_part = line.get_part_by_tex(from_name.capitalize())
line[-1].highlight(from_part.get_color())
amount_color = {
"USD" : GREEN,
"BTC" : YELLOW,
"LD" : YELLOW,
}.get(self.denomination, WHITE)
line.highlight_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(LaggedStart(
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.highlight(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_submobjects(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(*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.highlight(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.highlight(BITCOIN_COLOR)
banking = TextMobject("Banking system")
banking.next_to(protocol, RIGHT, MED_LARGE_BUFF)
banking.highlight(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.scale_to_fit_height(1)
dollar.highlight(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)).highlight(GREEN)
cash.scale_to_fit_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.highlight(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.highlight_by_tex("Owe", RED)
result.highlight_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(
LaggedStart(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_submobjects, 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(*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(LaggedStart(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.highlight(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(LaggedStart(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.highlight(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.highlight(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(LaggedStart(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_submobjects(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 = map(title.get_part_by_tex, ["Private", "Public"])
private.highlight(self.private_color)
public.highlight(self.public_color)
secret = TextMobject("Secret")
secret.move_to(private, RIGHT)
secret.highlight(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_submobjects(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.highlight_by_tex("sk", self.private_color)
key.highlight_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(*map(SurroundingRectangle, keys))
rects.highlight(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(LaggedStart(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_submobjects(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_submobjects(RIGHT, buff = MED_LARGE_BUFF)
documents.to_corner(UP+LEFT)
signatures = VGroup()
for document in documents:
signature = get_cursive_name("Alice")
signature.highlight(self.signature_color)
line = document[1][-1]
signature.next_to(line, UP, SMALL_BUFF)
signatures.add(signature)
self.play(
FadeOut(self.title),
LaggedStart(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.highlight(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(*map(
TextMobject,
["Different messages", "Completely different signatures"]
))
words.arrange_submobjects(DOWN, aligned_edge = LEFT)
words.scale(1.3)
words.next_to(self.documents, RIGHT)
self.play(FadeIn(rect))
self.play(*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.highlight_by_tex("sk", self.private_color)
mob.highlight_by_tex("pk", self.public_color)
mob.highlight_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.highlight_by_tex("Signature", BLUE)
verify.highlight_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_submobjects(DOWN, buff = SMALL_BUFF)
zeros.next_to(brace, UP)
self.add(verify)
self.play(
GrowFromCenter(brace),
FadeIn(
zeros,
submobject_mode = "lagged_start",
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.highlight_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.highlight_by_tex("Signature", BLUE)
verify.highlight_by_tex("pk", GREEN)
brace = Brace(signature_word, UP)
signature = sha256_tex_mob("Signature")
signature.next_to(brace, UP)
signature.highlight(BLUE_C)
rhs = TextMobject("=", "True")
rhs.highlight_by_tex("True", YELLOW)
rhs.next_to(verify, RIGHT)
pk = verify.get_part_by_tex("pk")
sk = TextMobject("sk")
sk.highlight(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.highlight(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(*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_submobjects(DOWN, buff = MED_SMALL_BUFF)
copies.next_to(line, DOWN, buff = MED_SMALL_BUFF)
self.play(
LaggedStart(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.highlight(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.highlight(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,
submobject_mode = "lagged_start",
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.highlight(RED)
new_item = self.get_new_item(
"Only signed transactions are valid"
)
new_item.highlight(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(LaggedStart(
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 = map(self.get_new_item, [
"Anyone can add lines to the Ledger \\,",
"Only signed transactions are valid \\,",
"No overspending"
])
lines[2].highlight(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.highlight(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(
LaggedStart(
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.highlight_by_tex(
name.capitalize(),
self.get_color_from_name(name)
)
new_line.highlight_by_tex("100", GREEN)
self.add_line_to_ledger(new_line)
lines.add(new_line)
line = Line(LEFT, RIGHT)
line.scale_to_fit_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(
LaggedStart(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.highlight(YELLOW)
rects.set_stroke(width = 2)
last_rect = rects[-1]
last_rect.set_stroke(RED, 4)
rects.remove(last_rect)
invalid = TextMobject("Invalid")
invalid.highlight(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(*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(*map(TextMobject, [
"\\$100", "\\$50", "\\$0", "Overdrawn"
]))
lines.highlight(GREEN)
lines[-1].highlight(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(
LaggedStart(FadeIn, lines, run_time = 3),
LaggedStart(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.scale_to_fit_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.highlight(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(LaggedStart(
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.highlight(YELLOW)
ld.scale(0.8)
ld.move_to(amount, LEFT)
amounts.target.add(ld)
ledger_dollars = TextMobject("Ledger Dollars \\\\ ``LD'' ")
ledger_dollars.highlight(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.highlight_by_tex("LD", YELLOW)
title.highlight_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_submobjects(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.highlight_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.scale_to_fit_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_submobjects(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).highlight(YELLOW)
btc = BitcoinLogo()
eth = EthereumLogo()
ltc = LitecoinLogo()
logos = VGroup(ltc, eth, ld, btc)
cryptos = VGroup(btc, eth, ltc)
for logo in cryptos:
logo.scale_to_fit_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(*map(Write, words))
self.wait(2)
class DistributedLedgerScene(LedgerScene):
def get_large_network(self):
network = self.get_network()
network.scale_to_fit_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.scale_to_fit_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_submobjects(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.scale_to_fit_height(pi.get_height())
pi.ledger[0].highlight(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.highlight(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(LaggedStart(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.highlight(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.scale_to_fit_height, FRAME_HEIGHT - 2,
group.center
)
self.wait(2)
def broadcast_transaction(self):
payment = TextMobject(
"Alice", "pays", "Bob", "100 LD"
)
payment.highlight_by_tex("Alice", self.alice.get_color())
payment.highlight_by_tex("Bob", self.bob.get_color())
payment.highlight_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 = 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_submobjects, 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(*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_submobjects(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.highlight_by_tex("Alice", self.alice.get_color())
payment.highlight_by_tex("Bob", self.bob.get_color())
payment.highlight_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/np.linalg.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(*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.highlight(RED)
question.scale(1.5)
brace.highlight(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.highlight(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].highlight(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.highlight_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 = [LaggedStart(FadeIn, lightbulb)]
)
self.play(LaggedStart(
ApplyMethod, lightbulb,
lambda b : (b.highlight, 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.highlight_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.highlight(YELLOW)
message_text = message_brace.get_text("Message/file")
message_text.highlight(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.highlight(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(LaggedStart(
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(LaggedStart(
ApplyMethod, mob,
lambda m : (m.highlight, 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.highlight(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,
submobject_mode = "lagged_start",
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.highlight(YELLOW)
new_sha_text[0].highlight(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.highlight(RED)
everything = VGroup(*self.get_mobjects())
arrow.next_to(everything, DOWN)
words = TextMobject("Inverse is infeasible")
words.highlight(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.highlight(YELLOW)
new_digest = sha256_tex_mob("Challenge")
new_digest.replace(self.digest)
q_marks = TextMobject("???")
q_marks.move_to(self.message_text)
q_marks.highlight(BLUE)
self.play(
Transform(
self.digest, new_digest,
run_time = 2,
submobject_mode = "lagged_start",
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.highlight_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,
submobject_mode = "lagged_start"
))
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_submobjects(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.highlight(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(LaggedStart(
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(*filter(
lambda m : not m.is_subpath,
ledger.family_members_with_points()
))
self.play(
ShowCreation(arrow),
FadeIn(sha)
)
self.play(LaggedStart(
ApplyMethod, decomposed_ledger,
lambda m : (m.highlight, YELLOW),
rate_func = there_and_back
))
point = VectorizedPoint(sha.get_center())
point.set_fill(opacity = 1)
self.play(LaggedStart(
Transform, decomposed_ledger.copy(),
lambda m : (m, point),
run_time = 1
))
bit_iter = iter(digest)
self.play(LaggedStart(
ReplacementTransform,
VGroup(*[point.copy() for x in range(256)]),
lambda m : (m, bit_iter.next()),
))
self.remove(*self.get_mobjects_from_last_animation())
self.add(digest)
self.play(
GrowFromCenter(zeros_brace),
Write(zeros_words, run_time = 1)
)
self.play(LaggedStart(
ApplyMethod, zeros,
lambda m : (m.highlight, 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.highlight(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.highlight(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,
submobject_mode = "lagged_start"
))
self.wait()
self.play(self.nonce.restore)
self.play(
self.digest.restore,
submobject_mode = "lagged_start",
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.highlight(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.scale_to_fit_height(amount.get_height())
new_amount.highlight(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,
submobject_mode = "lagged_start",
))
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 = np.linalg.norm(vect)
if dist < 2:
outgoing_lines.append(line)
dots = VGroup()
for line in outgoing_lines:
dot = Dot(line.get_start())
dot.highlight(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 = np.linalg.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_submobjects(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(LaggedStart(FadeIn, blocks))
self.play(
Write(blocks_word),
LaggedStart(
ShowCreation, arrows,
run_time = 1,
lag_factor = 0.6,
)
)
self.wait()
for group in all_payments, all_proofs_of_work:
self.play(LaggedStart(
Indicate, group,
rate_func = there_and_back,
scale_factor = 1.1,
))
self.play(*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.highlight(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]).highlight(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,
submobject_mode = "lagged_start"
))
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(LaggedStart(
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(*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.scale_to_fit_height(amount.get_height())
new_amount.highlight(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.highlight, 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.highlight(proof_of_work.get_color())
num_pow_group.scale_to_fit_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.highlight(random_bright_color())
self.remove(num_pow)
self.add(num_pow)
prev_hash.highlight(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.highlight_by_tex("LD", YELLOW)
for name, color in zip(self.names, self.name_colors):
payment.highlight_by_tex(name, color)
signature = TextMobject("$\\langle$ Signature $\\rangle$")
signature.highlight(payment[0].get_color())
signature.next_to(payment, DOWN, SMALL_BUFF)
payment.add(signature)
factor = self.payment_height_to_block_height
payment.scale_to_fit_height(factor*rect.get_height())
payments.add(payment)
self.transaction_counter += 1
payments.add(TexMobject("\\dots").scale(0.5))
payments.arrange_submobjects(DOWN, buff = MED_SMALL_BUFF)
payments.next_to(h_line1, DOWN)
proof_of_work = TextMobject("Proof of work")
proof_of_work.highlight(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.highlight(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_submobjects(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.scale_to_fit_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(LaggedStart(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(LaggedStart(FadeIn, creatures, run_time = 1))
self.play(
LaggedStart(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.highlight_by_tex("signature", BLUE)
terms.highlight_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.scale_to_fit_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_submobjects(DOWN, buff = LARGE_BUFF)
everything.to_edge(LEFT)
self.play(LaggedStart(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.highlight_by_tex("LD", YELLOW)
for name in self.get_names():
payment.highlight_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_submobjects(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.scale_to_fit_height(1.5)
payments.target.move_to(block)
prev_hash = TextMobject("Prev hash")
prev_hash.highlight(BLUE)
prev_hash.scale_to_fit_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.highlight(GREEN)
guess.scale_to_fit_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]).highlight(YELLOW)
else:
digest = sha256_tex_mob(str(x))
digest.scale_to_fit_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].highlight(LIGHT_GREY)
new_transaction.highlight_by_tex("LD", YELLOW)
new_transaction.scale_to_fit_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(*map(TextMobject, [
"- ``Block reward''",
"- No sender/signature",
"- Adds to total money supply",
]))
comments.arrange_submobjects(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(*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.highlight(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].highlight(LIGHT_GREY)
new_top_payment[-1].highlight(YELLOW)
new_top_payment.scale_to_fit_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 = 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_submobjects(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.scale_to_fit_height(1)
payments.move_to(block)
block.add(payments)
prev_hash = TextMobject("Prev hash")
prev_hash.highlight(BLUE)
prev_hash.scale_to_fit_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.scale_to_fit_height(0.2)
nonce.next_to(block.get_bottom(), UP, SMALL_BUFF)
nonces.add(nonce)
digest = sha256_tex_mob(str(x) + str(block))
digest.scale_to_fit_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(*filter(
lambda m : m is not winner,
self.miners
))
nonces[winner_index].highlight(GREEN)
new_digest = sha256_tex_mob("Winner", 60)
VGroup(*new_digest[:60]).highlight(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.highlight_by_tex(miner_name, LIGHT_GREY)
top_line.highlight_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.scale_to_fit_height(0.5)
lines.arrange_submobjects(
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.highlight(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.highlight(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),
*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_submobjects(
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.highlight(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.highlight(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.highlight_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(LaggedStart(FadeIn, bubble))
self.play(self.bob.change, "confused", words)
self.wait(2)
###
def get_fraud_block(self):
block = self.get_block()
block.scale_to_fit_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.highlight(color)
mob.scale_to_fit_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.highlight_by_tex(name, self.get_color_from_name(name))
payment.highlight_by_tex("LD", YELLOW)
payments = VGroup(
TexMobject("\\vdots"),
payment,
TexMobject("\\vdots")
)
payments.arrange_submobjects(DOWN)
payments.scale_to_fit_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.scale_to_fit_height(alice.get_height())
miners.arrange_submobjects(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.scale_to_fit_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").highlight(BLUE)
for mob, vect in (proof_of_work, DOWN), (prev_hash, UP):
mob.scale_to_fit_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.highlight(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.highlight(GREEN)
self.play(
Indicate(proof_of_work),
winner.change, "hooray"
)
block = winner.block.copy()
proof_of_work.highlight(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.highlight(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.scale_to_fit_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 = 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.highlight(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.highlight, 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.highlight(color)
mob.scale_to_fit_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.scale_to_fit_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.scale_to_fit_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.highlight(color)
ideas.arrange_submobjects(
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(LaggedStart(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.highlight(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.highlight(GREEN)
VGroup(*digest[:60]).highlight(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.highlight(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_submobjects(DOWN)
everyone.scale_to_fit_height(FRAME_HEIGHT - LARGE_BUFF)
everyone.to_corner(UP+LEFT)
self.play(Transform(miner_block, target))
self.play(LaggedStart(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]).highlight(YELLOW)
new_n_zeros_mob = TexMobject(str(n_zeros))
new_n_zeros_mob.move_to(n_zeros_mob)
new_n_zeros_mob.highlight(n_zeros_mob.get_color())
new_pow = self.get_rand_int_mob()
new_pow.replace(proof_of_work, dim_to_match = 1)
new_pow.highlight(proof_of_work.get_color())
self.play(
Transform(n_zeros_mob, new_n_zeros_mob),
Transform(
digest, new_digest,
submobject_mode = "lagged_start"
),
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.scale_to_fit_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_submobjects(
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.scale_to_fit_height(0.5)
logo.next_to(example, LEFT)
example[0].highlight(color)
self.add(title, h_line)
self.play(
FadeIn(examples[0]),
DrawBorderThenFill(logos[0])
)
self.wait()
self.play(*[
LaggedStart(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.gradient_highlight(GREEN_B, GREEN_D)
# logo.scale_to_fit_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.scale_to_fit_height(0.75)
logo.next_to(title, LEFT)
title.add(logo)
title.to_edge(UP)
h_line = Line(LEFT, RIGHT)
h_line.scale_to_fit_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_submobjects(
DOWN,
buff = MED_LARGE_BUFF,
aligned_edge = LEFT
)
rewards.next_to(h_line, DOWN)
for reward in rewards:
reward[1].highlight(YELLOW)
footnote = TextMobject(
"$^*$ Extrapolating from the 25 BTC reward period"
)
footnote.scale(0.5)
footnote.to_corner(DOWN+RIGHT)
self.play(LaggedStart(
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.highlight_by_tex(tex, color)
equation[-1].highlight(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.highlight_by_tex("Alice", BLUE_C)
payment.highlight_by_tex("Bob", MAROON)
payment.highlight_by_tex("BTC", YELLOW)
payment.move_to(2.5*UP)
fee = TextMobject("And leaves", "0.001 BTC", "to the miner")
fee.highlight_by_tex("BTC", YELLOW)
fee.next_to(payment, DOWN)
signature = TextMobject(
"$\\langle$Alice's digital signature$\\rangle$"
)
signature.highlight(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.highlight(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_submobjects(RIGHT, SMALL_BUFF)
title.next_to(block, UP)
brace = Brace(payments_rect, RIGHT)
limit = brace.get_text(
"Limited to\\\\",
"$\\sim 2{,}400$", "transactions"
)
limit.highlight_by_tex("2{,}400", RED)
self.add(title, block)
self.remove(payments)
self.play(
GrowFromCenter(brace),
Write(limit)
)
self.play(LaggedStart(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].highlight("#faa61a")
visa_logo.sort_submobjects()
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_submobjects(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(LaggedStart(DrawBorderThenFill, visa_logo))
self.play(LaggedStart(FadeIn, avg_rate))
self.wait()
self.play(LaggedStart(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.highlight_by_tex("BTC", YELLOW)
fees.highlight_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.highlight(YELLOW)
title.save_state()
title.shift(DOWN)
title.set_fill(opacity = 0)
topics = VGroup(*map(TextMobject, [
"Merkle trees",
"Alternatives to proof of work",
"Scripting",
"$\\vdots$",
"(See links in description)",
]))
topics.arrange_submobjects(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 = [LaggedStart(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 update_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.scale_to_fit_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)
]).highlight(WHITE)
exchanges = VGroup(*[
VGroup(*[
exchange.copy()
for x in range(3)
]).arrange_submobjects(RIGHT, buff = 2*LARGE_BUFF)
for y in range(3)
]).arrange_submobjects(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("\\$").highlight(GREEN),
TexMobject("\\$").highlight(GREEN),
TexMobject("\\$").highlight(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").highlight(YELLOW),
TextMobject("LD").highlight(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("\\$").highlight(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.scale_to_fit_height(5)
screen_rect.to_edge(LEFT)
self.play(
DrawBorderThenFill(logo[0]),
LaggedStart(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].highlight(YELLOW)
title[0].set_stroke(RED, 2)
self.add(title)
# logos = VGroup(
# BitcoinLogo(), EthereumLogo(), LitecoinLogo()
# )
# for logo in logos:
# logo.scale_to_fit_height(1)
# logos.add(TexMobject("\\dots").scale(2))
# logos.arrange_submobjects(RIGHT)
# logos.next_to(title, RIGHT, LARGE_BUFF)
# self.add(logos)
block_chain = self.get_block_chain()
block_chain.arrows.highlight(RED)
block_chain.blocks.gradient_highlight(BLUE, GREEN)
block_chain.scale_to_fit_width(FRAME_WIDTH-1)
block_chain.set_stroke(width = 12)
self.add(block_chain)