0x00 前言

混社区的时候(QQ群)总是听到大佬们聊到nep,好奇心驱使下就去neo官网找资料,然鹅,什么都没找到。后来就请教大佬,才知道nep是neo一系列提案,文档并不在neo官网,在这里。但是很奇怪的是我到目前为止只听说到了nep2,nep5和nep6,其余的几个提案似乎没什么人讲,以后有机会我再仔细了解下。nep2提案是一套加密私钥的算法,nep5提案是发布token相关的,nep6则是定义了标准化的neo钱包数据结构。由于我现在了解的最详尽的是nep2和nep6(好几个sdk源码都撸了一遍),而且nep2和nep6也是相辅相成密不可分,所以这里我就先主要从源码角度分析下nep2和nep6. 注: 本文行文逻辑 新账户 => nep2加解密 => 添加到nep6钱包

0x01 私钥

和几乎所有的加密货币一样,NEO的账户也是用了基于椭圆曲线的公私钥生成算法,在NEO的账户体系中,公钥由私钥计算而来,地址又由公钥计算而来,可以说只要掌握了私钥,就完全掌握了这个账户。数学原理请移步这里下载密码学书籍学习。 NEO的私钥是随机生成的长度为32的字节数组:

源码位置:neo/Wallets/Wallet.cs/CreateAccount()

 byte[] privateKey = new byte[32];using (RandomNumberGenerator rng = RandomNumberGenerator.Create()){rng.GetBytes(privateKey);}
复制代码

由于各个节点新账户的生成完全在本地进行,所以必须保证随机数生成器完全随机也就是安全随机才能真正确保账户的唯一性以及安全性,这里我研究了不同平台采取的安全随机数策略,首先就是neo内核C#版本采用的RandomNumberGenerator类,这个随机数生成算法以当前系统runtime环境参数作为熵源产生随机数,虽然执行效率比System.Random要慢上两个数量级,但是产生的结果却是安全的。

这里我还想说一下我在开发NEO钱包小程序的时候遇到的问题,那就是微信小程序并不提供安全的随机数生成算法,同时也不支持node内置的crypto,这让我纠结了很久,因为没有安全的随机数生成算法,那么这个钱包几乎就是不可用的。我曾想过:

  • 用户当前的经纬度,加速度,海拔
  • 用户拍照并对照片进行哈希

等方法来作为熵源,但是第一种密钥空间太小,第二种没办法实现。后来我发现在每次获取用户授权数据的时候,会收到一段加密的字符串。我研究了下这个加密算法,主要是AES-128-CBC,而且每次解密初始向量都是不同的,长度也完全满足需求,因此这段加密字符串可以认为是安全随机。

源码位置:NewEconoLab/NeoWalletForWeChat/blob/master/src/utils/random.js

export async function getSecureRandom(len) {wepy.showLoading({ title: '获取随机数种子' });let random = ''const code = await this.getLoginCode();const userinfo = await this.getUserInfo();console.log(code)random = SHA256(code + random).toString()random = SHA256(userinfo.signature + random).toString()random = SHA256(userinfo.encryptedData + random).toString()random = SHA256(userinfo.iv + random).toString()console.log(random)wepy.hideLoading();return random.slice(0, len)
}
复制代码

0x02 公钥

NEO从私钥计算公钥的算法和比特币是一样的,这部分讲的最好的当然是《Mastering BitCoin》中的第四章(下载连接),其中不仅详尽生动的讲解了比特币公私钥生成原理,而且辅助了大量的插图便于理解。比特币在生成公钥的时候选取的曲线是secp256k1曲线,而NEO选取的则是secp256r1。在StackOverflow上也有关于这两个曲线哪个更安全的讨论,详情点击连接,但是这个不在我的讨论范围。下面是secp256r1定义:

源码位置:neo/Cryptography/ECC/ECCurve.cs

        /// <summary>/// 曲线secp256r1/// </summary>public static readonly ECCurve Secp256r1 = new ECCurve(BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.AllowHexSpecifier),BigInteger.Parse("00FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", NumberStyles.AllowHexSpecifier),BigInteger.Parse("005AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", NumberStyles.AllowHexSpecifier),BigInteger.Parse("00FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", NumberStyles.AllowHexSpecifier),("04" + "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296" + "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5").HexToBytes());
复制代码

以上源码是NEO中secp256r1标准椭圆曲线的定义,哪怕不从密码学角度来看,就这参数的长度就给人一种想狗带的感觉。 生成公钥的时候,私钥需要乘上一个预先定义在曲线上的基点,获得的结果就是公钥。这个基点被称为G,所有的NEO节点的G都是相同的,也就是Secp256r1定义中最后那个特别长的字节数组。 《Mastering BitCoin》中的介绍如下:

_K = k * G

where k is the private key, G is the generator point, and K is the resulting public key, a point on the curve. Because the generator point is always the same for all bitcoin users, a private key k multiplied with G will always result in the same public key K. The relationship between k and K is fixed, but can only be calculated in one direction, from k to K. That’s why a bitcoin address (derived from K) can be shared with anyone and does not reveal the user’s private key (k)._

在NEO core中,这部分代码在KeyPair类中,但是由于计算部分主要是关于ECC的,所以我就不贴了。

0x03 地址

前文已经说过neo的地址是由公钥计算来的,但是其实还并不准确,这中间还是有很复杂的过程的。首先根据私钥生成账户的代码在NEP6Wallet类中:

源码位置:neo/Implementations/Wallets/NEP6/NEP6Wallet.cs

     public override WalletAccount CreateAccount(byte[] privateKey){KeyPair key = new KeyPair(privateKey);  //根据私钥生成公私钥对NEP6Contract contract = new NEP6Contract   //生成合约{Script = Contract.CreateSignatureRedeemScript(key.PublicKey),  //合约脚本ParameterList = new[] { ContractParameterType.Signature },ParameterNames = new[] { "signature" },Deployed = false   //不需要部署的鉴权合约};NEP6Account account = new NEP6Account(this, contract.ScriptHash, key, password){Contract = contract};AddAccount(account, false);return account;}
复制代码

从源码中可以看出,在生成新账户时,会根据公钥创建一个鉴权合约,创建合约的代码在Contract类的CreateSignatureRedeemScript方法中:

源码位置:neo/SmartContract/Contract.cs

      public static byte[] CreateSignatureRedeemScript(ECPoint publicKey){using (ScriptBuilder sb = new ScriptBuilder()){sb.EmitPush(publicKey.EncodePoint(true));//push公钥编码后的字节数组sb.Emit(OpCode.CHECKSIG);return sb.ToArray();}}
复制代码

这个方法会返回合约的脚本,地址就是根据这个脚本的哈希值得来的。在生成地址的时候,会传入这个合约脚本的哈希值:

源码位置:neo/Wallets/Wallet.cs

      public static string ToAddress(UInt160 scriptHash){byte[] data = new byte[21];data[0] = Settings.Default.AddressVersion;Buffer.BlockCopy(scriptHash.ToArray(), 0, data, 1, 20);return data.Base58CheckEncode();}
复制代码

在生成地址的时候,首先申请21字节缓冲区,缓冲区首字节设置为地址版本校验位,后20字节copy自合约哈希的前20个字节,然后对这个缓冲区进行base58加密得到的值就是我们的地址。 整体流程和BieCoin对比如下:

第一张比较丑的流程图是我画的NEO地址生成过程,第二张是从《Mastering BitCoin》书中截取的比特币地址生成流程,通过对比可以看出,除了NEO的地址是根据合约脚本哈希值而BItCoin是Sha256+RIPEMD160之后的摘要生成之外,两者的地址计算过程几乎一摸一样。

0x04 nep2

上文中已经从私钥到地址的整个流程都分析完了,如果是使用NEO账户的话,到上一小节,已经完全够了。从本小节往后讲的都是关于账户安全和账户管理的部分。 nep2是为了确保NEO账户私钥安全而提出的私钥加密提案,在提案里详细讲解了加密和解密的参数以及流程规范。 nep2分为两个部分,一个是加密,另一个是解密。加密的代码如下:

源码位置:neoWallets/KeyPair.cs

         public string Export(string passphrase, int N = 16384, int r = 8, int p = 8){using (Decrypt()){//获取地址合约脚本哈希UInt160 script_hash = Contract.CreateSignatureRedeemScript(PublicKey).ToScriptHash();//获取地址string address = Wallet.ToAddress(script_hash);//获取地址摘要前四字节byte[] addresshash = Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).ToArray();//计算scrypt keybyte[] derivedkey = SCrypt.DeriveKey(Encoding.UTF8.GetBytes(passphrase), addresshash, N, r, p, 64);byte[] derivedhalf1 = derivedkey.Take(32).ToArray();byte[] derivedhalf2 = derivedkey.Skip(32).ToArray();//aes加密byte[] encryptedkey = XOR(PrivateKey, derivedhalf1).AES256Encrypt(derivedhalf2);byte[] buffer = new byte[39];//校验位buffer[0] = 0x01;buffer[1] = 0x42;buffer[2] = 0xe0;//将地址摘要前四字节写入缓存Buffer.BlockCopy(addresshash, 0, buffer, 3, addresshash.Length);//密文写入缓存Buffer.BlockCopy(encryptedkey, 0, buffer, 7, encryptedkey.Length);//base58加密return buffer.Base58CheckEncode(); }}
复制代码

这个算法就是完全依据nep2提案的标准进行实现的,需要说明的是在最后的数据格式里,前三字节是校验位,之后四个字节是地址的哈希值,最后是密钥的密文,之所以构造这样的数据结构,是因为在解密的时候还需要从中提取地址哈希用于获取scrypt key。加密流程图如下:

而解密的过程则是和加密相反:

源码位置:neo/Wallets/Wallet.cs

  public static byte[] GetPrivateKeyFromNEP2(string nep2, string passphrase, int N = 16384, int r = 8, int p = 8){if (nep2 == null) throw new ArgumentNullException(nameof(nep2));if (passphrase == null) throw new ArgumentNullException(nameof(passphrase));//base58解密byte[] data = nep2.Base58CheckDecode();//格式校验if (data.Length != 39 || data[0] != 0x01 || data[1] != 0x42 || data[2] != 0xe0)throw new FormatException();byte[] addresshash = new byte[4];//读取地址哈希Buffer.BlockCopy(data, 3, addresshash, 0, 4);//计算scrypt key 这里结果和加密的 scrypt key需要相同byte[] derivedkey = SCrypt.DeriveKey(Encoding.UTF8.GetBytes(passphrase), addresshash, N, r, p, 64);byte[] derivedhalf1 = derivedkey.Take(32).ToArray();byte[] derivedhalf2 = derivedkey.Skip(32).ToArray();byte[] encryptedkey = new byte[32];Buffer.BlockCopy(data, 7, encryptedkey, 0, 32);//aes解密获取私钥byte[] prikey = XOR(encryptedkey.AES256Decrypt(derivedhalf2), derivedhalf1);//计算公钥Cryptography.ECC.ECPoint pubkey = Cryptography.ECC.ECCurve.Secp256r1.G * prikey;//获取账户合约脚本哈希UInt160 script_hash = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash();//计算地址string address = ToAddress(script_hash);//验证解密结果if (!Encoding.ASCII.GetBytes(address).Sha256().Sha256().Take(4).SequenceEqual(addresshash))throw new FormatException();return prikey;}
复制代码

解密所使用的scrypt参数需要和加密过程相同,不然无法得出相同的scrypt key,也就无法解出privateKey。下面是nep2解密流程:

0x05 nep6

nep6是NEO为了给不同的钱包应用提供统一的数据格式标准而制定的,所有实现了nep6协议的钱包应用,其钱包数据都是可以通用的。 新建钱包的时候需要指定新钱包的路径以及名称:

源码位置:neo/Implementations/Wallets/NEP6/NEP6Wallet.cs/NEP6Wallet(string path, string name = null)

                this.name = name;this.version = Version.Parse("1.0");this.Scrypt = ScryptParameters.Default;this.accounts = new Dictionary<UInt160, NEP6Account>();this.extra = JObject.Null;
复制代码

同时,每个NEP6钱包都可以保存多个NEP6Account对象,也就是说每个钱包里可以有多个地址账户。 NEP6的账户类里并不存储私钥,而是存储的加密后的nep2key,用户在导入nep6钱包后,如果想获取到账户私钥信息,就需要用户手动输入对应账号的passphrase才可以。这里需要注意的是,由于每个钱包只有一份Scrypt参数,所以在nep6钱包里的账户是不能指定不同的scrypt参数的。 nep6的钱包保存成文件的时候是以json的格式保存的,账户转json的代码如下:

源码位置:neo/Implementations/Wallets/NEP6/NEP6Account.cs

      public JObject ToJson(){JObject account = new JObject();account["address"] = Wallet.ToAddress(ScriptHash);//地址account["label"] = Label; //账户标签account["isDefault"] = IsDefault;account["lock"] = Lock; account["key"] = nep2key;//nep2keyaccount["contract"] = ((NEP6Contract)Contract)?.ToJson();//账户合约account["extra"] = Extra; //补充信息return account;}
复制代码

nep6钱包转Json代码如下:

源码位置:neo/Implementations/Wallets/NEP6/NEP6Wallet.cs

     public void Save(){JObject wallet = new JObject();wallet["name"] = name; //钱包名wallet["version"] = version.ToString(); //钱包版本wallet["scrypt"] = Scrypt.ToJson(); //scrypt加密参数wallet["accounts"] = new JArray(accounts.Values.Select(p => p.ToJson()));//账户转jsonwallet["extra"] = extra;File.WriteAllText(path, wallet.ToString());}
复制代码

以上就是NEO创建账户及钱包管理账户的全部内容,由于本人技术有限,难免疏漏错误之处,万望多多指教。 另外,本人开发的NEO微信钱包小程序已经上线微信小程序商城,大家可以搜索 “NEO”进入钱包试用。小程序基于NEL ThinSDK-ts进行开发,源码发布于NEL github仓库, 地址是 :

github.com/NewEconoLab…

小程序钱包主要功能基本完成并测试通过,但是尤待优化补充欢迎各位提交代码或者提出宝贵意见。如果您需要GAS或者NEO进行小程序的测试,可以发邮件到 jinghui@wayne.edu 联系我,我可以给您转一些测试网的GAS。

最后,本文发布之后我会着手NEP协议的汉化,希望感兴趣的朋友帮助我一起完成这个任务:github.com/Liaojinghui…

进技术群交流:795681763

原文链接:my.oschina.net/u/2276921/b…

NEO从源码分析看nep2与nep6相关推荐

  1. NEO从源码分析看NEOVM

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 前言 这篇文章是为下一篇<NEO从源码分析看UTXO转账交易>打前站,为交易的构造及执行的一些技术基础做 ...

  2. NEO从源码分析看网络通信

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 前言 NEO被称为中国版的Ethereum,支持C#和java开发,并且在社区的努力下已经把SDK拓展到了js,py ...

  3. NEO从源码分析看数字资产

    0x00 引言 比特币是泡沫么?也许是的.毕竟这东西除了用来炒,干什么实事都感觉肉疼.但是有人将比特币泡沫和郁金香泡沫相提并论就很气人了,郁金香什么鬼,长那么一年,开那么几天,泡沫还没破呢,郁金香已经 ...

  4. NEO从源码分析看共识协议

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 概论 不同于比特币使用的工作量证明(PoW)来实现共识,NEO提出了DBFT共识算法.DBFT改良自股权证明算法(P ...

  5. 从NEO源码分析看DBFT共识协议

    作者:廖京辉 原文链接:https://mp.weixin.qq.com/s?__biz=MzUzNDQwNDQ0Mw==&mid=2247483919&idx=1&sn=59 ...

  6. NEO源码分析之UTXO全局资产

    作者:廖京辉 原文链接:https://mp.weixin.qq.com/s?__biz=MzUzNDQwNDQ0Mw==&mid=2247483941&idx=1&sn=4a ...

  7. 从vuex源码分析module与namespaced

    使用vue已经有半年有余, 在各种正式非正式项目中用过, 开始专注于业务比较多, 用到现在也遇见不少因为理解不深导致的问题. 有问题就有找原因的勇气, 所以带着问题搞一波. 带着问题看源码 所以来整理 ...

  8. 4hutool源码分析:DateUtil(时间工具类)-格式化时间(万字长文源码分析,学大佬如何写代码)

    技术活,该赏 点赞再看,养成习惯 看本篇文章前,建议先对java源码的日期和时间有一定的了解,如果不了解的话,可以先看这篇文章: 万字博文教你搞懂java源码的日期和时间相关用法 关联文章: huto ...

  9. Netty源码分析系列之常用解码器(下)——LengthFieldBasedFrameDecoder

    扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,Spring源码分析和Java并发编程文章. 前言 在上一篇文章中分析了三个比较简单的解码器,今天接着分析最后一个常用的解码器:Leng ...

最新文章

  1. 解析Java多线程的两点误区你必知的
  2. java怎么导入文件_怎么将文件导入java
  3. swiper轮播后hover无效问题解决案例
  4. 性能测试四十五:性能测试策略
  5. C++静态数据成员和静态成员函数
  6. CImage类 from http://www.cnblogs.com/afarmer/archive/2012/03/31/2427273.html
  7. 小米回应有品特殊激励需放弃年终奖……外媒说,荷兰运营商KPN允许华为有限参与5G网络建设;传苹果挖角英特尔5G工程师...
  8. python爬虫源码项目_32个Python爬虫实战项目,满足你的项目慌(带源码)
  9. 容器编排技术 -- Kubernetes kubectl expose命令详解
  10. win10环境下VTK7.1的编译和Qt调用VTK的详细教程二(VS2013编译支持Qt的VTK库)
  11. 随想录(熟练掌握uml)
  12. 通信原理笔记(樊昌信第七版)
  13. CSV 文件中的字段中的开头和结尾上,可能会存在空格或制表符,但是该如何处理呢?
  14. c语言tc2.0编译器下载,c语言tc2.0下载
  15. Docker下安装Redis超详细步骤
  16. 【MS SQL Server】SQL Server2005下载地址
  17. 建立保持时间及违例解决方法
  18. 2021AAPM大赛第一名技术报告Designing an Iterative Network for Fanbeam-CTwith Unknown Geometry 阅读笔记
  19. Mssql2k之数据库管理
  20. 基于时间片延长的轮转调度算法

热门文章

  1. Handlebars.js 模板引擎
  2. 组合搜索(combinatorial search)在算法求解中的应用
  3. 我的第一个用Hibernate框架写的小例子
  4. Linux (CentOS)增加删除用户
  5. linux android ndk r9b JNI 开发过程(COCOS2D-X配置)
  6. 安卓常用功能——已封装好
  7. android利用WebView实现浏览器的封装
  8. Java字符串的子串
  9. adpcm 解码音量小_Oriolus 1795解码耳放评测:仅仅蓝牙还不够,我要的是“真无线”...
  10. 【数字信号处理】离散时间系统 ( 离散时间系统概念 | 线性时不变系统 LTI - Linear time-invariant )