From 9edccb116eb44d567086f0259e52b744f42ae09e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Tue, 4 Jul 2017 11:51:04 -0700 Subject: [PATCH] Up to double spending attack in crypto --- crypto.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 387 insertions(+), 12 deletions(-) diff --git a/crypto.py b/crypto.py index 890b30be..988048ca 100644 --- a/crypto.py +++ b/crypto.py @@ -3520,18 +3520,393 @@ class TwoBlockChains(DistributedBlockChainScene): 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.dither() + +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.dither(3) + +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.dither() + + 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.dither() + + 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.dither() + + 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.dither(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.dither(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.dither() + + 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.dither() + + 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.revert_to_original_skipping_status() + 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.dither(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" + ] + + for winner in winners: + self.dither(2) + 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.play(*anims) + + + ##### + + def dither(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