TIC 地址采用 b64u 编码

This commit is contained in:
陆柯 2020-02-21 10:13:05 +08:00
parent 2a0692e9be
commit bdec8f6acb
2 changed files with 78 additions and 92 deletions

130
index.js
View File

@ -83,10 +83,6 @@ module.exports = {
return /^((02|03)?[a-fA-F0-9]{64}|04[a-fA-F0-9]{128})$/.test(pubkey) // "d2f186a630f5558ba3ede10a4dd0549da5854eab3ed28ee8534350c2535d38b0"
}
,
isAddress (address) {
return /^[m|t|d|T][123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{33}$/.test(address) // && address.length>25 && bs58check.decode(address.slice(1)) && ['A'].indexOf(address[0]>=0)) {
}
,
isSignature(signature){
return /^[a-fA-F0-9]{128,144}$/.test(signature)
}
@ -304,7 +300,7 @@ module.exports = {
return null
}
,
pubkey2position (pubkey, {coin}={}){
pubkey2position (pubkey, {coin}={}){ // tic, btc, eth 的 position 都是 20节=40字符的。
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
if(this.isPubkey(pubkey)){
if (coin==='ETH'){
@ -322,11 +318,11 @@ module.exports = {
return null
}
,
position2address(position, {coin, netType}={}){
if (!position) return null
position2address(position, {coin, net, format}={}){
if (!/^[\da-fA-F]{40}$/.test(position)) return null // 不论 tic, btc, eth其 position 都是 40字符的。
coin = my.COIN_LIST.indexOf(coin)>=0?coin:my.COIN
let address
if (coin==='ETH'){ // 对以太坊,按照 EIP55把纯位置转换为大小写敏感能自我验证的hex地址
if (coin==='ETH'){ // 对以太坊,按照 EIP55把纯位置转换为大小写敏感能自我验证的hex地址。仍然为20节=40符。
position = position.toLowerCase().replace('0x', '')
let hash = keccak('keccak256').update(position).digest('hex')
address = '0x'
@ -338,9 +334,9 @@ module.exports = {
}
}
return address
}else if (coin === 'BTC'){ // 对比特币把纯位置转换为大小写敏感能自我验证的bs58check地址
}else if (coin === 'BTC'){ // 对比特币把纯位置转换为大小写敏感能自我验证的bs58check地址先加前缀再加校验共25字节再转base58。得到2634个字符大多数34个。
let prefix
switch (netType) {
switch (net) {
case 'mainnet': prefix='00'; break; // pubkey hash => 1
case 'mainnetSh': prefix='05'; break; // script hash => 3
case 'testnet': prefix='6f'; break; // testnet pubkey hash => m or n
@ -351,52 +347,60 @@ module.exports = {
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
return address
}else {
}else { // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64u(base64 for url) 地址。
let prefix
switch (netType){
case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m
case 'testnet': prefix='7F'; break; // '7F'=>t
case 'devnet': prefix='5A'; break; // '5A'=>d
default: prefix='42' // 默认暂且为 42为了兼容已经运行的链。
switch (net){
// Base58: https://en.bitcoin.it/wiki/List_of_address_prefixes
// Base64: https://baike.baidu.com/item/base64
case 'mainnet': prefix='4c'; break; // Base58: 0x42=66 => T, Base64: base64 T=0x13=0b00010011 => 0b010011xx = 0x4c~4f
case 'testnet': prefix='b4'; break; // Base58: 0x7f=127,0x80=128 => t, Base64: t=0x2d=0b00101101 => 0b101101xx = 0xB4~B7
case 'devnet': prefix='74'; break; // Base58: 0x90 => d, Base 64: d=0x1d=0b00011101 => 0b 011101xx = 0x74~77
default: prefix='4c'
}
address=bs58check.encode(Buffer.from(prefix+position, 'hex')) // wallet import format
let checksum = this.hash(this.hash(position)).slice(0,6) // 添加 checksum 使得能够检测大小写错误。注意,不包含 prefix这样更纯粹、专注。
// address = this.hex2eip55(prefix + position + checksum) // 前缀1节位置20节校验3节共24节=48字符能够完全转化为8个色彩再转eip55。
address = this.hex2b64u(prefix + position + checksum) // 实际采用 b64u (named by luk.lu as base 64 for url), 共 32字符。
return address
}
return null
}
,
address2position(){
if (/^0x[\da-fA-F]{40}$/.test(address)){
return address.toLowerCase()
}else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)){
let hex = this.b58c2hex(address)
if (hex) {
return hex.slice(2) // 去除网络前缀
}
}else if (/^[Tt][0-9a-zA-Z\-_]{31}$/.test(address)){ // 格式合法
let hex = this.b64u2hex(address)
let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/)
if (this.hash(this.hash(position)).slice(0,6) === checksum) {
return position
}
}
return null
}
,
isAddress(address){
if (/^0x[\da-fA-F]{40}$/.test(address)){
return 'ETH'
}else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) // 格式合法
&& this.b58c2hex(address)){ // 内容合法
return 'BTC'
}else if (/^[Ttd][0-9a-zA-Z\-_]{31}$/.test(address)){ // 格式合法
let b64 = address.replace('-', '+').replace('_', '/')
let hex = Buffer.from(b64, 'base64').toString('hex')
let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/)
return this.hash(this.hash(position)).slice(0,6) === checksum
}
return null
}
,
pubkey2address (pubkey, option={}) { // pubkey 应当是string类型
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (this.isPubkey(pubkey)) {
let h256 = crypto.createHash('sha256').update(Buffer.from(pubkey, 'hex')).digest()
let h160 = crypto.createHash('ripemd160').update(h256).digest('hex')
let prefix
if (option.coin==='TIC'){
switch (option.netType){
case 'mainnet': prefix='42'; break; // '42'=>T, '6E'=>m
case 'testnet': prefix='7F'; break; // '7F'=>t
case 'devnet': prefix='5A'; break; // '5A'=>d
default: prefix='42' // 默认暂且为 42为了兼容已经运行的链。
}
}else if (option.coin==='BTC'){
switch (option.netType) {
case 'mainnet': prefix='00'; break; // 1
case 'testnet': prefix='6f'; break; // m or n
case 'p2sh': prefix='05'; break; // 3
default: prefix='00'
}
}else if (option.coin==='ETH'){
// 注意必须要用非压缩的64字节的公钥的buffer并去掉开头的 04。
return '0x' + keccak('keccak256').update(Buffer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
// 或 const { keccak256 } = require('ethereumjs-util'); keccak256(Buffer.from(pubkey.slice(2),'hex)).toString('hex').slice(40)
// 或 const { Keccak } = require('sha3'); new Keccak('').update(Bufer.from(pubkey.slice(2),'hex')).digest('hex').slice(-40)
}else {
return null
}
var wifAddress=bs58check.encode(Buffer.from(prefix+h160,'hex')) // wallet import format
return wifAddress
}
return null
return this.position2address(this.pubkey2position(pubkey, option), option)
}
,
secword2seed(secword, pass) { // 遵循bip39的算法。和 ether.HDNode.mnemonic2Seed 结果一样是64字节的种子。
@ -624,11 +628,26 @@ module.exports = {
}
}
,
hex2b64u(hex){
if (/^[0-9a-fA-F]+$/.test(hex)) {
return Buffer.from(hex,'hex').toString('base64').replace(/\+/g,'-').replace(/\//g, '_')
}
return null
}
,
b64u2hex(b64u){
if (/^[0-9a-zA-Z\-_]+$/.test(b64u)){
let b64 = b64u.replace(/\-/g, '+').replace(/_/g, '/')
return Buffer.from(b64, 'base64').toString('hex')
}
return null
}
,
hex2eip55(hex){
if (typeof(hex)==='string' && hex.length===64) {
if (/^(0x)?[\da-fA-F]*$/.test(hex)) {
hex = hex.toLowerCase().replace('0x', '')
let hash = keccak('keccak256').update(hex).digest('hex')
result = '0x'
let result = ''
for (var i = 0; i < hex.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
result += hex[i].toUpperCase()
@ -643,18 +662,23 @@ module.exports = {
,
// test: https://iancoleman.io/bitcoin-key-compression/
// compress: https://hacpai.com/article/1550844562914
compressPubkey(uncompressed){
compressPubkey(uncompressed){ // 把 04xy 的非压缩公钥 转成 02x 或 03x 的压缩公钥
let [all, x, y]=uncompressed.toLowerCase().match(/^04(.{64})(.{64})$/)
let compressed
if (/[1,3,5,7,9,b,d,f]$/.test(y)){
return '03'+x // y为奇数=>前缀03
compressed = '03'+x // y为奇数=>前缀03
}else{
return '02'+x // y为偶数=>前缀02
compressed = '02'+x // y为偶数=>前缀02
}
if (this.decompressPubkey(compressed)===uncompressed) {
return compressed
}
return null // 非压缩公钥有错误。
}
,
// uncompress: https://stackoverflow.com/questions/17171542/algorithm-for-elliptic-curve-point-compression/53478265#53478265
// https://en.bitcoin.it/wiki/Secp256k1
decompressPubkey(compressed){
decompressPubkey(compressed){ // 把 02x 或 03x 的压缩公钥 转成 04xy 的非压缩公钥
// Consts for secp256k1 curve. Adjust accordingly
const prime = new BigInt('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', 16) // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
const pIdent = new BigInt('3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0c', 16) // prime.add(1).divide(4);

View File

@ -1,38 +0,0 @@
const bigInt = require("big-integer");
// Consts for P256 curve. Adjust accordingly
const two = new bigInt(2),
// 115792089210356248762697446949407573530086143415290314195533631308867097853951
prime = two.pow(256).subtract( two.pow(224) ).add( two.pow(192) ).add( two.pow(96) ).subtract(1),
b = new bigInt( '41058363725152142129326129780047268409114441015993725554835256314039467401291' ),
// Pre-computed value, or literal
// 28948022302589062190674361737351893382521535853822578548883407827216774463488
pIdent = prime.add(1).divide(4);
function pad_with_zeroes(number, length) {
var retval = '' + number;
while (retval.length < length) {
retval = '0' + retval;
}
return retval;
}
/**
* Point decompress NIST curve
* @param {string} Compressed representation in hex string
* @return {string} Uncompressed representation in hex string
*/
function ECPointDecompress( comp ) {
var signY = new Number(comp[1]) - 2;
var x = new bigInt(comp.substring(2), 16);
// y^2 = x^3 - 3x + b
var y = x.pow(3).subtract( x.multiply(3) ).add( b ).modPow( pIdent, prime );
// If the parity doesn't match it's the *other* root
if( y.mod(2).toJSNumber() !== signY ) {
// y = prime - y
y = prime.subtract( y );
}
return '04' + pad_with_zeroes(x.toString(16), 64) + pad_with_zeroes(y.toString(16), 64);
}
console.log(ECPointDecompress('035d77c1e3eac37f685aeea2ae872c4e7e4d159756e57601db3bcccbc549f360b2'))