【主要内容】
今天继续分析从github上获取的开源代码怎么实现简单区块链的入门知识,共用时间47分钟。
(此外整理作笔记花费了约60分钟)
详细学习过程见文末学习过程屏幕录像。
今天进一步完成了【blockchain.py】文件源代码的部分细节代码的学习分析,继续添加了更详细的批注,今天主要纠结与对发送方的加了私钥签名信息的验签的理解。
相关资料通过百度实时搜索学习。感谢以下博文的分享:
https://www.jianshu.com/p/6a39610122fa(这篇应当是作者转载的)
https://www.pycryptodome.org/en/latest/src/api.html (模块的官方文档)
https://www.jianshu.com/p/7d79562717b3
https://www.cnblogs.com/pcheng/p/9629621.html
https://www.cnblogs.com/lyhabc/p/7995254.html
【学习笔记】
一、对密码学中的核心交互过程有了比较清晰的认识
首先博文(https://www.cnblogs.com/pcheng/p/9629621.html)中的表达非常便于我理解
加密和签名都是为了安全性考虑,但略有不同。常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改。这里举2个例子说明。
第一个场景:战场上,B要给A传递一条消息,内容为某一指令。
RSA的加密过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A传递自己的公钥给B,B用A的公钥对消息进行加密。
(3)A接收到B加密的消息,利用A自己的私钥对消息进行解密。
在这个过程中,只有2次传递过程,第一次是A传递公钥给B,第二次是B传递加密消息给A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。
第二个场景:A收到B发的消息后,需要进行回复“收到”。
RSA签名的过程如下:
(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
(2)A用自己的私钥对消息加签,形成签名,并将加签的消息和消息本身一起传递给B。
(3)B收到消息后,在获取A的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是A回复的。
在这个过程中,只有2次传递过程,第一次是A传递加签的消息和消息本身给B,第二次是B获取A的公钥,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给B,防止了消息内容的篡改。
但是,综合两个场景你会发现,第一个场景虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给A。第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥验签来获得,并不能防止泄露。所以在实际应用中,要根据情况使用,也可以同时使用加密和签名,比如A和B都有一套自己的公钥和私钥,当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。
(不过此处作者写的代码是用java完成的)
然后深入,发现博文(https://www.jianshu.com/p/6a39610122fa)的描述稍微清晰些:
RSA 密码算法与签名
RSA是一种公钥密码算法,RSA的密文是对代码明文的数字的 E 次方求mod N 的结果。也就是将明文和自己做E次乘法,然后再将其结果除以 N 求余数,余数就是密文。RSA是一个简洁的加密算法。E 和 N 的组合就是公钥(public key)。
对于RSA的解密,即密文的数字的 D 次方求mod N 即可,即密文和自己做 D 次乘法,再对结果除以 N 求余数即可得到明文。D 和 N 的组合就是私钥(private key)。
算法的加密和解密还是很简单的,可是公钥和私钥的生成算法却不是随意的。
使用Publick/Private秘钥算法中,加密主要用对方的公钥,解密用自己的私钥。签名用自己的私钥,验签用对方的公钥。
加密解密:公钥加密,私钥解密
签名验签:私钥签名,公钥验签
无论是加密机密还是签名验签都使用同一对秘钥对,
二、对【blockchain.py】文件的理解批注第四天
今天的学习笔记都作到了注释文本中(学习分析的思维过程可见我的屏幕录像):
今天主要是从全局实现过程进行研读批注,每个方法函数的内部细节还没有细致思考。
下面是到今天为止已对【blockchain.py】进行详细注释的源代码
'''
'''
title : blockchain.py
description : A blockchain implemenation
author : Adil Moujahid
date_created : 20180212
date_modified : 20180309
version : 0.5
usage : python blockchain.py
python blockchain.py -p 5000
python blockchain.py --port 5000
python_version : 3.6.1
Comments : The blockchain implementation is mostly based on [1].
I made a few modifications to the original code in order to add RSA encryption to the transactions
based on [2], changed the proof of work algorithm, and added some Flask routes to interact with the
blockchain from the dashboards
References : [1] https://github.com/dvf/blockchain/blob/master/blockchain.py
[2] https://github.com/julienr/ipynb_playground/blob/master/bitcoin/dumbcoin/dumbcoin.ipynb
'''
from collections import OrderedDict
import binascii
import Crypto
import Crypto.Random
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
'''
#关于RSA模块的详细说明博文:https://www.jianshu.com/p/7d79562717b3
(下面是这篇博文的开头部分:)
Crypto 算法库在 python 中最初叫 pycrypto,这个作者有点懒,好几年没有更新,后来就有大佬写了个替代库 pycryptodome。这个库目前只支持 python3,安装也很简单pip install就行了!
详细的用法可以看看 官方文档(https://www.pycryptodome.org/en/latest/src/api.html)
常见对称密码在 Crypto.Cipher 库下,主要有:DES 3DES AES RC4 Salsa20
非对称密码在 Crypto.PublicKey 库下,主要有:RSA ECC DSA
哈希密码在 Crypto.Hash 库下,常用的有:MD5 SHA-1 SHA-128 SHA-256
随机数在 Crypto.Random 库下
实用小工具在 Crypto.Util 库下
数字签名在 Crypto.Signature 库下
#----------------------
这篇博文对加密-解密,签名-验签的过程讲得很详细
https://www.cnblogs.com/pcheng/p/9629621.html
#注意点:
RSA加密对明文的长度有所限制,规定需加密的明文最大长度=密钥长度-11(单位是字节,即byte),
所以在加密和解密的过程中需要分块进行。而密钥默认是1024位,即1024位/8位-11=128-11=117字节。
所以默认加密前的明文最大长度117字节,解密密文最大长度为128字。那么为啥两者相差11字节呢?
是因为RSA加密使用到了填充模式(padding),即内容不足117字节时会自动填满,
用到填充模式自然会占用一定的字节,而且这部分字节也是参与加密的。
'''
from Crypto.Signature import PKCS1_v1_5
import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
import requests
from flask import Flask, jsonify, request, render_template
from flask_cors import CORS
MINING_SENDER = "THE BLOCKCHAIN" #矿工的挖矿(即成功创建一个区块)奖励的交易发出方
MINING_REWARD = 1
MINING_DIFFICULTY = 2
class Blockchain:
def __init__(self):
self.transactions = [] #此列表用于记录目前在区块链网络中已经经矿工确认合法的交易信息,等待写入新区块中的交易信息。
self.chain = [] #此列表表示区块链对象本身。
self.nodes = set() #建立一个无序元素集合。此集合用于存储区块链网络中已发现的所有节点信息
#Generate random number to be used as node_id
self.node_id = str(uuid4()).replace('-', '') #此测试区块链网络中,此节点的Id标识 。
#在这个类初始化的方法中,就创建了【创世区块】,且成为本区块链网络的第一个加入的区块(下一行代码完成)
self.create_block(0, '00') #创世区块诞生
#--添加一个区块链网络中新发现的一个节点到已知节点集合中。(添加的节点的信息是节点的url信息中的核心部分,即纯域名部分或相对路径)
def register_node(self, node_url):
"""
添加一个区块链网络中新发现的一个节点到已知节点集合中。
"""
#Checking node_url has valid format
#检查节点的格式,通过urlparse方法将这个节点的url分割成六个部分
#--下面是对urlparse的研究结论:
'''
#来自模块:from urllib.parse import urlparse
urlparse方法的效果
将给定的url分解为以下五部分:
[0]:'scheme'
[1]:'netloc'
[2]:'path'
[3]:'query'
[4]:'fragment'
一、
http://www.baidu.com/m/
分解信息与返回的五个部分:
fragment:''
hostname:'www.baidu.com'
netloc:'www.baidu.com'
password:None
path:'/m/'
port:None
query:'a=21&b=23'
scheme:'http'
username:None
[0]:'http'
[1]:'www.baidu.com'
[2]:'/m/'
[3]:'a=21&b=23'
[4]:''
二、
http:8080//www.baidu.com/m/
分解信息与返回的五个部分:
fragment:''
hostname:None
netloc:''
password:None
path:'8080//www.baidu.com/m/'
port:None
query:'a=21&b=23'
scheme:'http'
username:None
[0]:'http'
[1]:''
[2]:'8080//www.baidu.com/m/'
[3]:'a=21&b=23'
[4]:''
三、
ftp://username:pass@www.baidu.com/m/
分解信息与返回的五个部分:
fragment:''
hostname:'www.baidu.com'
netloc:'username:pass@www.baidu.com'
password:'pass'
path:'/m/'
port:None
query:'a=21&b=23'
scheme:'ftp'
username:'username'
[0]:'ftp'
[1]:'username:pass@www.baidu.com'
[2]:'/m/'
[3]:'a=21&b=23'
[4]:''
'''
parsed_url = urlparse(node_url)
if parsed_url.netloc: #如果网络地址不为空,那么就添加没有http://之类修饰的纯的地址,如:www.baidu.com
self.nodes.add(parsed_url.netloc)
elif parsed_url.path: #如果网络地址为空,那么就添加相对Url的路径
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
else:
raise ValueError('Invalid URL') #说明这是一个非标准的Url
#--矿工检查发起一次交易广播的发送者提供的私钥签名是否与它自己的公钥(sender_address)签名的交易相对应。
def verify_transaction_signature(self, sender_address, signature, transaction):
"""
矿工检查发起一次交易广播的发送者提供的私钥签名是否与它自己的公钥(sender_address)签名的交易相对应。
"""
public_key = RSA.importKey(binascii.unhexlify(sender_address)) #获取发送方的公钥
#binascii模块用于二进制与ASCII编码的相互转换,unhexlify方法的作用是:(https://www.cnblogs.com/lyhabc/p/7995254.html)
#unhexlify方法还原用十六进制表示的二进制数据(即是说返回结果为一个字符串,这里究竟是字符串,还是二进制的byte流?)。需要一个参数:hexstr必须包含偶数个十六进制数字(大写或小写),这儿其实是发送方的公钥的十六进制byte字节。
#RSA.importKey方法导入标准格式编码的RSA密钥(公共或私有半密钥【什么叫私有半密钥,这是机器 翻译的。】)。
#---这篇博文似乎作了详尽说明:https://www.jianshu.com/p/6a39610122fa
#---------------------------------------------------------------
#使用发送方的公钥来验证发送方的签名的开始
verifier = PKCS1_v1_5.new(public_key) #--这儿使用了Crypto.Signature子库中的一种填充方法:PKCS1_v1_5(另有一种填充方法:PKCS1_OAEP)
#通过PKCS1_v1_5.new方法将发送者的公钥填充为(资料显示为签名或验签,此处是签名还是验签呢?)
h = SHA.new(str(transaction).encode('utf8'))
'''
#h = SHA.new(str(transaction).encode('utf8'))的理解:
transaction是当前矿工要验证的一次交易的交易信息本身(是一个字典)
encode('utf8')表示将这个交易信息中的所有字符都转换为utf-8编码
SHA.new方法使用这个信息(是不是已经被转换成了一个符串?)来算出一个新的HASH值。
'''
return verifier.verify(h, binascii.unhexlify(signature)) #验证发送方的签名 #误报错误,可以直接运行
'''
上一行,使用发送方的公钥处理后的对象verifier(对公钥作了什么处理,我还没有理解)
通过verify方法对信息内容得到的新hash值 h 与发送者的 私钥 签名 字节串 signature
进行 签名合法性验证,以验证交易信息transaction的发送方的确是公钥sender_address或public_key拥有者
'''
#----如果verify_transaction_signature方法已验证发起一次交易广播的发送者提供的私钥签名合法,则将此次交易添加到待完成交易列表中。(此交易将等待写入下一次新产生的一个区块中)
def submit_transaction(self, sender_address, recipient_address, value, signature):
"""
如果verify_transaction_signature方法已验证发起一次交易广播的发送者提供的私钥签名合法,则将此次交易添加到待完成交易列表中。(此交易将等待写入下一次新产生的一个区块中)
"""
#OrderedDict类来自于模块:collections
#OrderedDict用于对字典对象中的元素进行排序,OrderedDict会根据放入元素的先后顺序进行排序,也可以通过sort进行指定元素信息的排序
transaction = OrderedDict({'sender_address': sender_address,
'recipient_address': recipient_address,
'value': value})
if sender_address == MINING_SENDER:
#如果当前交易内容是对矿工的挖矿奖励,那么——
#--将此交易信息添加到待处理(等待写入下一下新创建的区块中)交易信息列表(变量是:transactions)中-----
self.transactions.append(transaction)
return len(self.chain) + 1
else:
#如果当前交易是节点到节点之间的转账交易 ,那么——
#--下一句代码验证交易的合法性,即通过交易发送方的交易发送方地址(这儿就是公钥)和发送方的私钥签名来验证。
transaction_verification = self.verify_transaction_signature(sender_address, signature, transaction)
#--将此交易信息添加到待处理(等待写入下一下新创建的区块中)交易信息列表(变量是:transactions)中-----
if transaction_verification:
#---如果验证发送方发起的交易合法,那么——
self.transactions.append(transaction)
return len(self.chain) + 1
else:
return False
#--将已经写入交易信息的一个新区块添加到区块链的末尾,其中previous_hash指定了此区块之前的一个区块,因此就链接在其之后。
def create_block(self, nonce, previous_hash):
"""
将已经写入交易信息的一个新区块添加到区块链的末尾,其中previous_hash指定了此区块之前的一个区块,因此就链接在其之后。
"""
#在这个新的区块中,包含了以下信息:
#'block_number':当前区块编号,这儿就是区块链的顺序号,即链上的第几块区块
#'timestamp':生成此块(应当是将交易信息写入此块)的时间戳
#'transactions':所有写入到当前区块的交易信息
#'nonce':矿工通过算力证明(工作量证明)成功得到的Number Once值,证明其合法创建了一个区块(当前区块)
#'previous_hash':在当前区块添加到区块链之前,区块链原来的最后一个区块的哈希值。(此值表明了当前区块的上一区块的位置,直到定位连接的作用)
block = {'block_number': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.transactions,
'nonce': nonce,
'previous_hash': previous_hash}
# Reset the current list of transactions
#因为已经将待处理(等待写入下一下新创建的区块中)交易信息列表(变量是:transactions)中的所有交易信息写入了区块并添加到区块链末尾,则此处清除此列表中的内容
self.transactions = []
#将当前区块添加到区块链末端
self.chain.append(block)
return block
def hash(self, block):
"""
Create a SHA-256 hash of a block
"""
# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
#此方法通过算法获取一个Number Once值,以通过工作量证明得到生成一个新区块的权限,返回这个Number Once值
def proof_of_work(self):
"""
Proof of work algorithm
此方法通过算法获取一个Number Once值,以通过工作量证明得到生成一个新区块的权限,返回这个Number Once值
"""
last_block = self.chain[-1] #取出区块链现在的最后一个区块
last_hash = self.hash(last_block) #取出这最后 一个区块的哈希值(散列值)
#下面通过循环来使Number Once的值从0开始每次增加1来进行尝试,直到得到一个符合算法要求 的Number Once值为止
nonce = 0
while self.valid_proof(self.transactions, last_hash, nonce) is False:
nonce += 1
return nonce #返回这个符合算法要求的Number Once值。
#此函数是上一个方法函数的附属部分,用于检查哈希值是否满足挖掘条件。此函数用于工作函数的证明中。
def valid_proof(self, transactions, last_hash, nonce, difficulty=MINING_DIFFICULTY):
"""
检查哈希值是否满足挖掘条件。此函数用于工作函数的证明中。
"""
guess = (str(transactions)+str(last_hash)+str(nonce)).encode()
guess_hash = hashlib.sha256(guess).hexdigest() #!没有读懂
return guess_hash[:difficulty] == '0'*difficulty #!没有读懂
def valid_chain(self, chain):
"""
check if a bockchain is valid
"""
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
#print(last_block)
#print(block)
#print("\n-----------\n")
# Check that the hash of the block is correct
if block['previous_hash'] != self.hash(last_block):
return False
# Check that the Proof of Work is correct
#Delete the reward transaction
transactions = block['transactions'][:-1]
# Need to make sure that the dictionary is ordered. Otherwise we'll get a different hash
transaction_elements = ['sender_address', 'recipient_address', 'value']
transactions = [OrderedDict((k, transaction[k]) for k in transaction_elements) for transaction in transactions]
if not self.valid_proof(transactions, block['previous_hash'], block['nonce'], MINING_DIFFICULTY):
return False
last_block = block
current_index += 1
return True
def resolve_conflicts(self):
"""
Resolve conflicts between blockchain's nodes
by replacing our chain with the longest one in the network.
"""
neighbours = self.nodes
new_chain = None
# We're only looking for chains longer than ours
max_length = len(self.chain)
# Grab and verify the chains from all the nodes in our network
for node in neighbours:
print('http://' + node + '/chain')
response = requests.get('http://' + node + '/chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# Check if the length is longer and the chain is valid
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
# Replace our chain if we discovered a new, valid chain longer than ours
if new_chain:
self.chain = new_chain
return True
return False
# Instantiate the Node
app = Flask(__name__)
CORS(app)
# Instantiate the Blockchain
blockchain = Blockchain()
@app.route('/')
def index():
return render_template('./index.html')
@app.route('/configure')
def configure():
return render_template('./configure.html')
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.form
# Check that the required fields are in the POST'ed data
required = ['sender_address', 'recipient_address', 'amount', 'signature']
if not all(k in values for k in required):
return 'Missing values', 400
# Create a new Transaction
transaction_result = blockchain.submit_transaction(values['sender_address'], values['recipient_address'], values['amount'], values['signature'])
if transaction_result == False:
response = {'message': 'Invalid Transaction!'}
return jsonify(response), 406
else:
response = {'message': 'Transaction will be added to Block '+ str(transaction_result)}
return jsonify(response), 201
@app.route('/transactions/get', methods=['GET'])
def get_transactions():
#Get transactions from transactions pool
transactions = blockchain.transactions
response = {'transactions': transactions}
return jsonify(response), 200
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
@app.route('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.chain[-1] #---当前区块链中最长链的最后一个区块,blockchain指当前测试的区块链网络本身,是由类blockchain实例化而得到的对象。
nonce = blockchain.proof_of_work() #---取得了一个可以实现优先创建(挖出)下一个区块的工作量证明的 Number Once值。
# 由于当前去检查发现的发布广播的交易发起者的一次交易完成,且成功通过工作量算法证明,成功创建(挖出)了一个新区块
# 则此矿工将获得奖励,下面确认的等待写入新区块的交易信息,就是这个奖励交易(就是直接给此矿工一笔数字代币)的信息,详见下面方法的定义位置。
blockchain.submit_transaction(sender_address=MINING_SENDER, recipient_address=blockchain.node_id, value=MINING_REWARD, signature="")
#我理解为是区块链本身作为发送方, #此交易接收方正是当前节点本身就使用node_id,#此参数是奖励的数字代币金额,#最后一个参数是发起交易方的私钥签名,由于发起交易方是区块链本身,因此签名为空(个人理解 )
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block) #取出当前区块链中最长链的最后一个区块的Hash值,用作要新加入区块的前导HASH(用于连接)
block = blockchain.create_block(nonce, previous_hash) #将新区块(此区块包含了两条交易信息:一条是之前由交易发起者广播的交易 ,另一条是矿工的奖励交易)添加到区块链的最后。
response = {
'message': "New Block Forged",
'block_number': block['block_number'],
'transactions': block['transactions'],
'nonce': block['nonce'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
@app.route('/nodes/register', methods=['POST'])
def register_nodes():
#从浏览器客户端获取通过post传递过来的节点信息
values = request.form
nodes = values.get('nodes').replace(" ", "").split(',')
if nodes is None:
return "Error: Please supply a valid list of nodes", 400
for node in nodes:
#通过blockchain实例的注册方法将节点添加到节点集合中去
blockchain.register_node(node)
response = {
'message': 'New nodes have been added',
'total_nodes': [node for node in blockchain.nodes],
}
return jsonify(response), 201
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is authoritative',
'chain': blockchain.chain
}
return jsonify(response), 200
@app.route('/nodes/get', methods=['GET'])
def get_nodes():
nodes = list(blockchain.nodes)
response = {'nodes': nodes}
return jsonify(response), 200
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='127.0.0.1', port=port)
【学习后记】
今天的学习比较艰难,因为一是对基础的加密算法完全一无所知,此外对于所用到的加密解密 模块也没有基础准备,所以耗时多但效果差。
所以可见,年轻时不断广泛阅读的知识储备是多么重要。年岁渐长,真是感觉日渐艰难了。
不过由此呢,突然对密码学有了兴趣,便又去翻阅了下面的文章:
http://mini.eastday.com/mobile/180122111943090.html
【关于坚持自学的例行说明】
最后例行说明下,我为什么要坚持自学。
一、为什么一把年纪还在学习
放弃很多去聚餐,去HI歌,去游玩,去看电影,去追剧……的时间,然后进行着这个年纪似乎已不应当再进行的学习,引来身边人们无尽的不解与鄙夷甚至可怜……
但我不想放弃终身学习的誓言。
因为——
我对我今天的生活现状并不认同!
罗伯特清崎告诉过我们,反省自己当下的生活是不是自己想要的,这难道不是最好的动力与答案?
走过了大半生,然后才发现曾经、当下所正在进行的人生并不是自己想要的,那是一种怎样的体验?
只有心中真切的感受才能回答这个问题,而任凭再丰富的语言也是无法描绘出来的。
经历半生的跋涉,却发现走得并不正确,有多少人有勇气承认自己过去的一切都是错误的呢?
而我愿意告诉过去的我:“你错了!”
那么已经历半生错误,年岁之大又压于头顶,还有希望从这架的梯子的半端重新爬下,再蹒跚着爬上另一架梯子吗?
我宁愿相信还有希望!
这便是我为什么要继续坚持终身学习下去的全部理由。
二、这个年纪还在学这些技术有意义吗
纯的技术对这把年纪其实已没有意义。
但兴趣可以超越意义。
但技术可以引来思想的变革,这才是意义。
投资自己的头脑 ,改革自己的思想,这是最保值,更长远的投资,过去我从来没有投资过,错过太多,那就从投资自己头脑开始吧。
罗伯特清崎告诉我们,真正的富有是时间的富有;真正的自由是可以决定自己愿意做什么的自由。
因为我愿意做我兴趣所在的事,所以我希望我有自由选择的那一天,虽然今天离那一天可能还是那么遥远,但我愿意相信,每天多赶几步,离希望就更近一步。
再者,虽然我可能再已无法完全完整的掌握这些技术了,但技术本身却可以启迪心的觉醒,激发灵感,那么只要多了解一点,我相信我将离那个正离我而去跑得越来越快的未来更近一点,不至于被未知的那个未来抛弃得太远。
于是我怎能放弃追逐求索的步伐?
我要坚信:感觉太迟的时候,也许还不算太迟。
感谢一直以来关注我,鼓励我的你!
若不嫌弃这一个到了高龄才长大的可笑可叹的我,请不吝赐教。
我的q号是:578652607,敬候你的指点。
【同步语音笔记】
【学习过程屏幕录屏】
网友评论