标题
- 聊一聊区块链-波场技术, 只聊技术
- 前言
- 背景
- 波场链
- 区块链是什么
- 共识
- 工作量证明 Pow
- 权益证明 Pos
- 节点
- 区块
- 代币 Trx
- Trc20-usdt
- 转账
- 写在最后
聊一聊区块链-波场技术, 只聊技术
前言
最近因项目使用 tron 协议接入区块链, 故对其做了一番研究, 先把相关资料整理一遍, 供大家学习使用;
网上的这部分资料很少, 所以学习起来也是遇到了很多困难, 尤其是里面很多新的概念, 理解起来有一定的难度。比如说去中心化、地址、加密算法、算法因子、私钥含义、助记词、trc 协议、智能合约、usdt 等等;
背景
很多人接触区块链, 大多是通过接触 usdt 这种中充当稳定资产(也称泰达币)角色开始的, usdt 是什么? 下面我们探讨一下这个问题。
USDT 是一种将加密货币与法定货币美元挂钩的虚拟货币 , 一种基本衡量单位的代币; Tether 公司推出的基于稳定价值货币美元(USD)的代币 Tether USD(下称 USDT), 1USDT=1 美元, 用户可以随时使用 USDT 与 USD 进行 1:1 兑换。
公司对外宣称严格遵守 1: 1 的准备金保证, 即每发行 1 个 USDT 代币, 其银行账户都会有 1 美元的资金保障。用户可以在 Tether 平台进行资金查询, 以保障透明度这样然后 usdt 稳定资产发行者通过存入三方银行机构法定资产, 来保证我的 usdt 是有保证的, 不会超发或者失去赔付能力;
我们怎么知道 Tether 公司所说的真实性呢?
这一点我们通过 Tether 官网 (https://tether.to/en/supported-protocols/) 进行查看, 进入透明度 (https://tether.to/en/transparency/) 展示界面, 可以看到其发行了多少枚代币, 以及质押情况。
可以看到, 不光有 USDT, 还有 ESDT、CSDT ; 其实 U 是代表美国, E 代表欧洲, C 代表中国的意思。
波场链
TRON 是一个开源同时支持智能合约功能的公共区块链平台, 也叫波场链。其创始人是中国人孙宇晨, 他拍过巴菲特的午餐, 牛。好了, 废话不多说, 介绍一些东西吧。
区块链是什么
既然我们很多时候提到了链, 那链到底是什么呢?
其实链可以理解为一个平台, 这个平台能够做一些事情。比如运行智能合约, 发行代币等等。当然构建一个去中心化的链就必须要实现一些机制, 比如构建信任机制, 这是去中心化核心中的核心。说到这里要引入一个概念: 共识。即如何在分布式的不信任环境中搭建一个信任的网络?
共识
在区块链系统中没有像银行一样的中心化机构, 所以在进行传输信息、价值转移时, 共识机制解决并保证每一笔交易在所有记帐节点上的一致性和 正确性问题。区块链的这种新的共识机制使其在不依靠中心化组织的情况下, 依然大规模高效协作完成运转。
工作量证明 Pow
工作量证明简单理解就是一份证明, 用来确认节点做过一定量的工作。监测工作的整个过程通常是极为低效的, 而通过对工作的结果进行认证来证明完成了相应的工作量, 则是-种非常高效的方式。比特币在区块的生成过程中使用了 PoW 机制, 要得到合理的随机数求解数学难题需要经过大量尝试计算, 通过查看记录和验证区块链信息的证明, 就能知道是否完成了指定难度系数的工作量。
权益证明 Pos
即把资产存在银行里, 银行会通过你持有数字资产的数量和时间给你分配相应的收益。采用 PoS 机制的加密货币资产, 系统会根据节点的持币数量和时间的乘积(币天数)给节点分配相应的权益。
还有一些其他的共识机制, 这里介绍了两种最为广泛和常见的共识机制。
节点
TRON 是一个由很多计算机组成的分布式网络, 计算机上需要运行可以验证区块和交易数据的软件, 也叫节点。您需要在系统上安装运行一个叫做客户端的软件, 才能运行一个节点。
“节点 “指的是一个正在运行的客户端软件。客户端是 TRON 的一个实现, 它可以验证每个区块的所有交易, 保持网络的安全和数据的准确性。目前 TRON 客户端是由 Java 语言实现, 它遵循 TRON 协议, 实现了 TRON 网络和区块链的功能。
每个人可以下载相关源代码, 并运行节点的客户端, 可以自行验证交易; 也可以理解”节点 “指的是一个正在运行的客户端软件。客户端是 TRON 的一个实现, 它可以验证每个区块的所有交易, 保持网络的安全和数据的准确性。
区块
区块是一个包含了一系列交易的集合, 另外一个区块还包含了前一个区块的哈希值, 这种实现方式将各个区块连接到一起形成链。
为了确保 TRON 网络上的所有节点保持一致的状态并就交易的历史达成一致, 我们将交易打包进区块, 实现了数百个交易同时被提交、确认以及同步到全网。每个新创建的区块都包含其父区块的 hash 值。在任何给定的时间, 网络上的几乎所有节点都对区块的数量和历史状态达成一致。
代币 Trx
TRX 是 TRON 网络上最主要的加密货币, 就像是以太坊的链上的代币就是 eth; TRX 是 TRON 区块链上帐户的基本单位, 在 tron 链上进行的交易活动花费以 trx 作为标准衡量。
TRX 可以被铸造、销毁、转移等行为; 有了这些功能, 我们才能理解后面为啥会有 TRC20-USDT 的概念了?
- TRC20 协议标准
我们知道 TRON 链支持运行智能合约, 而我们熟知的 TRC20, 其实就是一种合约, 这个合约里面实现了一些功能, 比如代币查询、转移、授权等等功能。
TRC-20 是为发行通证资产而制定的一套合约标准, 即遵守这一标准编写的合约都被认为是一个 TRC-20 合约。当各类钱包、交易所在对接 TRC-20 合约的资产时, 从这套合约标准中就可以知道这个合约定义了哪些函数、事件, 从而方便的进行对接。
要明白 trc20 是一种协议, 这个协议是波场 tron 链下面的一种, 还有 trx, trc10, trc721 等等, 而波场链跟 usdt 发行者公司合作, 写了一份智能合约, 该协议实现了几种功能, 如交易、查询、授权、事件监听等等, 我们在地址中转账看到的 trc20-usdt 就是执行了这个交易方法 transfer, 所以能够把一个地址中的 usdt 转移到另一个地址;
//trc20 协议中支持的方法contract TRC20 {function totalSupply() constant returns (uint theTotalSupply);function balanceOf(address _owner) constant returns (uint balance);function transfer(address _to, uint _value) returns (bool success);function transferFrom(address _from, address _to, uint _value) returns (bool success);function approve(address _spender, uint _value) returns (bool success);function allowance(address _owner, address _spender) constant returns (uint remaining);event Transfer(address indexed _from, address indexed _to, uint _value);event Approval(address indexed _owner, address indexed _spender, uint _value);}
这下会明白了? 为什么出现了 TRC20-USDT 这个词, 其原因就是如此。同理, 在 eth 链上实现一个支持 USDT 交易的智能合约, 那这个链就能通过 ETH 进行转账交易, 比如说 erc20-usdt。
Trc20-usdt
转账
要实现交易, 首先要得有地址, 自己地址, 对方地址, usdt, trx 燃料费; 然后这几个要素经过什么步骤才能达到目的?
- 创建交易 、离线签名、广播
先把代码贴出来:
/** * 发起 trc20 交易 (目标地址, 数量, 合约地址, 私钥) * 地址 默认为 usdt 合约地址 * @throws Throwable */public String sendTrc20(String toAddress, BigDecimal amount, String privateKey) throws Throwable {String ownerAddress = TronUtils.getAddressByPrivateKey(privateKey);JSONObject jsonObject = new JSONObject();jsonObject.put("contract_address", TronUtils.toHexAddress(USDT_CPNTRACT));jsonObject.put("function_selector", "transfer(address,uint256)");List<Type> inputParameters = new ArrayList<>();inputParameters.add(new Address(TronUtils.toHexAddress(toAddress).substring(2)));inputParameters.add(new Uint256(amount.multiply(decimal).toBigInteger()));String parameter = FunctionEncoder.encodeConstructor(inputParameters);jsonObject.put("parameter", parameter);jsonObject.put("owner_address", TronUtils.toHexAddress(ownerAddress));jsonObject.put("call_value", 0);jsonObject.put("fee_limit", 6000000L);String trans1 = HttpClientUtils.postJson(tronUrl + "/wallet/triggersmartcontract", jsonObject.toString());JSONObject result = JSONObject.parseObject(trans1);System.out.println("trc20 result:" + result.toJSONString());if (result.containsKey("Error")) {throw new RuntimeException("result.containsKey(\"Error\")");}JSONObject tx = result.getJSONObject("transaction");//填写备注tx.getJSONObject("raw_data").put("data", Hex.toHexString("备注信息".getBytes()));String txid = TronUtils.signAndBroadcast(tronUrl, privateKey, tx);if (txid != null) {System.out.println("txid:" + txid);return txid;}return null;}
创建交易
String ownerAddress = TronUtils.getAddressByPrivateKey(privateKey);JSONObject jsonObject = new JSONObject();jsonObject.put("contract_address", TronUtils.toHexAddress(USDT_CPNTRACT));jsonObject.put("function_selector", "transfer(address,uint256)");List<Type> inputParameters = new ArrayList<>();inputParameters.add(new Address(TronUtils.toHexAddress(toAddress).substring(2)));inputParameters.add(new Uint256(amount.multiply(decimal).toBigInteger()));String parameter = FunctionEncoder.encodeConstructor(inputParameters);jsonObject.put("parameter", parameter);jsonObject.put("owner_address", TronUtils.toHexAddress(ownerAddress));jsonObject.put("call_value", 0);jsonObject.put("fee_limit", 6000000L);String trans1 = HttpClientUtils.postJson(tronUrl + "/wallet/triggersmartcontract", jsonObject.toString());JSONObject result = JSONObject.parseObject(trans1);System.out.println("trc20 result:" + result.toJSONString());if (result.containsKey("Error")) {throw new RuntimeException("result.containsKey(\"Error\")");}JSONObject tx = result.getJSONObject("transaction");//填写备注tx.getJSONObject("raw_data").put("data", Hex.toHexString("备注信息".getBytes()));
先通过私钥获取自己的地址, 然后指定合约地址, 即 usdt 在波场的合约地址, 指定合约中的方法; 然后指定对方地址、附上燃料费 trx , 通过调用 /wallet/triggersmartcontract 创建交易; 至此第一步就算完成了;
这里需要说明, trx 燃料费的概念, 也就是支付给区块链节点的矿工费用; 如果没有 trx 交易是不成功的; 很多人疑惑, 为啥用交易所不需要 trx, 那是因为交易所帮你给付了, 用 web3 wallet 转, 必须支付 trx;
- 签名和广播
public static String signAndBroadcast(String tronUrl,String privateKey,JSONObject transaction)throws Throwable{if(tronUrl.endsWith("/")){tronUrl= tronUrl.substring(0,tronUrl.length() - 1);}Protocol.Transaction tx = packTransaction(transaction.toJSONString());byte[] bytes = signTransactionByte(tx.toByteArray(), ByteArray.fromHexString(privateKey));String signTransation = Hex.toHexString(bytes);JSONObject jsonObjectGB = new JSONObject();jsonObjectGB.put("transaction", signTransation);String url = tronUrl + "/wallet/broadcasthex";String transationCompelet1 = HttpClientUtils.postJson(url, jsonObjectGB.toString());JSONObject transationCompelet = JSONObject.parseObject(transationCompelet1);System.out.println("signAndBroadcast transationCompelet:" + transationCompelet.toJSONString());if (transationCompelet.getBoolean("result")) {return transationCompelet.getString("txid");} else {logger.error(String.format("签名交易失败: %s",transationCompelet1));return null;}}/** * 签名交易 * @param transaction * @param privateKey * @return * @throws InvalidProtocolBufferException * @throws NoSuchAlgorithmException */public static byte[] signTransactionByte(byte[] transaction, byte[] privateKey) throws InvalidProtocolBufferException, NoSuchAlgorithmException {ECKey ecKey = ECKey.fromPrivate(privateKey);Protocol.Transaction transaction1 = Protocol.Transaction.parseFrom(transaction);byte[] rawdata = transaction1.getRawData().toByteArray();MessageDigest digest = MessageDigest.getInstance("SHA-256");digest.update(rawdata,0,rawdata.length);byte[] hash= digest.digest();byte[] sign = ecKey.sign(hash).toByteArray();return transaction1.toBuilder().addSignature(ByteString.copyFrom(sign)).build().toByteArray();}
- 签名验证的原理
在已知交易发起者(contract owner)地址的情况下, 通过签名消息逆推公钥(recover), 并将公钥转换为地址, 与发起者地址进行比较。如果地址一致, 即为验证成功。
- 验证签名的方法
验证方法需要三个参数:
- 交易 id(即交易哈希, 通过 Transaction.rawData 计算 SHA256 得到)
- 签名消息(即 Transaction.signature)
- 发起者地址(即 Transaction.rawData.contract.parameter.ownerAddress, 其中 parameter 的类型是 com.google.protobuf.Any, 需要根据具体交易类型来进行 unpack 操作)
byte[] bytes = signTransactionByte(tx.toByteArray(), ByteArray.fromHexString(privateKey));String signTransation = Hex.toHexString(bytes);
- 广播
广播可理解为发送交易。任何与波场网络的交互行为都被称作为一笔交易。一笔交易可以是 TRX 转账、质押/解锁 TRX、触发智能合约等。
只有消耗资源的交易才会被记录在链上。前面提到了 trx 燃料费, 就是这里的消耗的资源; 当区块链的其他节点确认了你的交易, 并把此笔交易广播给其他人后, 这笔交易就算交易成功, 即同步到其他节点的数据库了;
wrapper.broadcastTransaction(signedTransaction); //return transaction hash if successfully broadcasted, otherwise the error codeString url = tronUrl + "/wallet/broadcasthex";String transationCompelet1 = HttpClientUtils.postJson(url, jsonObjectGB.toString());JSONObject transationCompelet = JSONObject.parseObject(transationCompelet1);
以上就是 trc20-usdt 转账的背后逻辑。下面讲讲 wallet 地址以及 wallet 地址的创建和生成;
账户与地址
TRON 采用账户模型, 账户的唯一标识为地址(address), 对账户操作需要私钥签名。帐户由一对加密密钥组成: 公钥和私钥。公钥映射为地址, 私钥用来对交易进行签名。这对密钥不但可以防止交易被篡改和伪造, 而且还可以证明交易确实是由发送方所发送。私钥由 64 个十六进制字符组成。公钥的生成基于私钥, 并使用椭圆曲线数字签名算法(https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)来生成对应的公钥。基于公钥可以计算出地址。
账户地址格式
TRON 网络账户地址有两种格式, 一种是 Hex 格式, 另一种是 Base58 格式。
Hex 格式: 用公钥 P 作为输入, 计算 SHA3 得到结果 H, 取 H 的最后 20 字节, 在前面填充一个字节 0x41 得到 Hex 格式地址。例如:
418840E6C55B9ADA326D211D818C34A994AECED808
Base58 格式: 对 hex 格式的地址进行 basecheck 计算得到 Base58 格式地址, 所有账户地址的第一个字符均为 T。例如:
TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL
我们经常看的和使用的就是 Base58 格式的地址。
- 钱包是什么
wallet 钱包可以理解为 管理账户中的地址和私钥的工具, 这些工具可以是 app, 或者网站; 我们称这些工具为钱包, 比如我们常用的钱包 imToken、tronLink 等等。在使用 web3 wallet 时, 经常会让我们主动创建或导入住记词、私钥的方式创建 wallet, 这后面的原理又是什么呢?
我们可以理解 wallet 钱包地址是这套算法中公钥, 这个地址是公开的, 别人可以向你进行交易等等; 而经常说的助记词就是把私钥经过可逆算法转换成了 12 个常见的英文字符串, 二者是等价的(这个过程和产生 wallet 地址、私钥算法不一样), 明白加密算法的人都知道, 加密算法一般不具备可逆向性的, 私钥能推导出公钥的, 反之不行。所以务必保护好你的私钥及代表私钥的助记词。
好了, 明白这些东西后, 那我们看代码:
/** * 离线创建地址 * * @return */public static Map<String, String> createAddress() {ECKey eCkey = new ECKey(random);String privateKey = ByteArray.toHexString(eCkey.getPrivKeyBytes());byte[] addressBytes = eCkey.getAddress();String hexAddress = ByteArray.toHexString(addressBytes);Map<String, String> addressInfo = new HashMap<>(3);addressInfo.put("address", toViewAddress(hexAddress));addressInfo.put("hexAddress", hexAddress);addressInfo.put("privateKey", privateKey);return addressInfo;}
在这个过程中, 涉及到了大量的算法相关的知识, 需要说明的是 tron wallet 的加密算法经过多次转换和加密的, 这个过程非常之复杂, 就不展开讲了。
地址查询
如果我们知道了一个 wallet 地址, 我们可以查询其 wallet 的交易情况, 比如 tron 链上的所有协议, 如 trx 交易、trc20-usdt 交易等等;
String specificWalletTransferUrl = urlAddress + blockWalletBean.monitorAddress + "/transactions/trc20";Map<String, String> paraMap = new HashMap<>();paraMap.put("limit", "30");paraMap.put("only_confirmed", "true");paraMap.put("contract_address", "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");String content = httpGet(specificWalletTransferUrl, paraMap);System.out.println("content:" + content);f (!StringUtils.isEmpty(content)) {JSONObject jsonObject = JSONObject.parseObject(content);JSONArray results = jsonObject.getJSONArray("data");//解析数据, 获取 wallet address 交易详细信息
区块扫描
public BigInteger getNowBlock() {String url = tronUrl + "/wallet/getnowblock";String httpRequest = HttpRequest.get(url).execute().body();JSONObject jsonObject1 = JSONObject.parseObject(httpRequest);return jsonObject1.getJSONObject("block_header").getJSONObject("raw_data").getBigInteger("number");}
写在最后
其实这个 wallet 、智能合约还有很多的功能, 我们经常听到有些人的被盗, 那些被盗的人怎么做到的呢, 我们该如何去防范呢? 这些东西需要我们深入研究才能明白其中的奥秘, 好了篇幅有限, 至此结束。