- A+
本文以比特币测试网络(bitcoin-testnet)作为开发试验环境,以结合Node.js实现PPkPub开源项目ODIN的标识注册功能作为具体应用案例,来说明如何利用多重签名交易形式来嵌入自定义数据并签名后广播,再被矿工节点确认存入区块链后,最终能被读取解析得到注册结果,这样就实现了一个从写入区块链到从区块链读取的完整过程。
一、了解ODIN开放标识的定义
ODIN 作为PPkPub 开放小组发布的第一个开源项目,是Open Data Index Name 即“开放数据索引命名标识”的缩写。广义上,ODIN 是指在网络环境下标识和交换数据内容索引的一种开放性系统,它遵从URI(统一资源标识符) 规范,并为基于数字加密货币区块链(BlockChain)的自主开放、 安全可信的数据内容管理和知识产权管理提供了一个可扩展的框架。它包括4 个组成要素:标识符、 解析系统、 元数据和规则(Policies) 。 狭义上,ODIN 是指标识任何数据内容对象的一种永久性开放标识符。
ODIN 可以被形象地理解为“数据时代的自主域名”,是基于比特币区块链定义并可扩展兼容更多区块链的完全开放、 去中心化的命名标识体系,相比传统的 DNS 域名拥有更多创新特性,可以很好地被应用到大数据、智能设备和物联网等新兴领域。
参考了XCP(合约币)和Mastercoin(万事达币)等数字加密货币的技术原理,ODIN的实现是通过将特定消息数据按比特币协议规范进行特定编码后,作为比特币交易广播到比特币网络上存入区块链。
每个ODIN信息包括以下特性:
(a) 一个比特币源地址(对应ODIN消息生成者)
(b) 一个比特币目的地址(对应受ODIN消息指向的目标个体,当消息生成者与受消息指向的目标个体相同时,该地址为空)
(c) 若干个1-of-N多重签名输出比特币地址公钥(经由ODIN数据包编码生成,实际生成交易时从ODIN设置数据中按顺序每提取32个字节,并在该32个字节的前部加上1个字节对应该字符串的长度值,总共33个字节对应一个比特币公钥,最后不足33个字节的自动在尾部追加二进制0填满直到正好达到33字节对应一个压缩公钥)
(d) 比特币源地址中有一定数量的比特币余额(建议有0.001BTC以上,用于生成从源地址发送到目的地址的若干有效交易条目以嵌入ODIN数据包。注: 因为比特币1-of-N多重签名交易的特点,这些比特币金额不会发生实际支出,将在下一个ODIN消息中被回收循环利用)
(e) 以比特币计的消息成本固定费用(缺省是0.0001 BTC),将支付给收录这个交易数据块的比特币网络矿工。
(f) 一个比特币找零地址(与上述第一项的比特币源地址相同,用于按照比特币交易协议将输入交易的比特币金额在生成若干条满足嵌入ODIN数据包的交易条目后多出的金额回收到消息发送者账户)
上述的特性(c)是技术实现的关键,ODIN数据块会嵌入到比特币交易的多重签名输出数据块中,是1-of-N输出,每个数据块的第一个公钥固定是发送者的,因而输出的币值可以赎回循环使用,存贮第二个到第N个公钥的地址空间用来存贮编码的ODIN消息数据。关于比特币多重签名交易的详细说明请参考比特币协议规范。
注:N建议取值不超过10。对于1条1-of-10多重签名输出仍无法容纳的ODIN数据块,可依样扩展存入第2条,第3条等更多条多重签名输出记录中即可。
每个ODIN信息数据块的格式按字节顺序定义如下:
第1-32字节 : 前缀特征标识,32个字节的ASCII字符串”P2P is future! ppkpub.org->ppk:0″(不含两侧的双引号)
第33字节 : 消息类型,1个字节。
第34字节到消息结束为按消息类型区分的不同消息数据,详见具体的ODIN 消息类型中的定义。
注:为了便于识别,每个ODIN消息都以32字节的ASCII字符串”P2P is future! ppkpub.org->ppk:0″(不含两侧的引号)作为前缀特征标识,这个字符串非常长,因而不可能把ODIN的特定交易和其它的比特币交易搞混。
二、将ODIN标识注册到区块链上的实例解析
下文是ODIN协议里对于“新注册ODIN标识”的具体消息定义:
————————————————————————————————————
新注册ODIN标识
比特币源地址:对应ODIN标识注册者
比特币目的地址:对应ODIN标识拥有者
消息数据块的格式按字节顺序定义如下:
第1-32字节 : 前缀特征标识,32个字节的ASCII字符串”P2P is future! ppkpub.org->ppk:0″(不含两侧的双引号)
第33字节 : 消息类型,1个字节,取值为ASCII字符R
第34字节 : 消息正文数据格式,1个字节。
取值定义:ASCII字符,
T 表示“UTF-8编码文本字符串”,
G 表示“经gzip算法压缩得到的二进制数据,需解压后可得到UTF-8编码的原始文本字符串”
第35-36字节: 消息正文数据字节长度,2个字节的无符号短整型二进制数据,取值范围为0-65535。
第37字节到消息正文指定长度结束是按字节存放的消息正文数据,需根据第34字节的数据格式取值来获得原始消息文本,为UTF-8编码的JSON格式字符串,对应一个JSON对象数据,说明如下:
{
”title”:”说明:个体名称字符串”,
”email”:”说明:个体的公开EMAIL,可选”,
”auth”:”说明:配置权限,取值定义见下方注释”,
”ap_list”:["说明:若干个数据访问点AP的URL数组,最少需填写一个",...,"xxxx"],
”catalog”:”说明:数据源类型,可选保留字段,待补充”
}
注:配置权限的取值说明:ASCII字符0,1或2
0表示“注册者或拥有者任一方都可以修改拥有者相关信息”,
1表示“只有注册者能修改拥有者相关信息”,
2表示“注册者和拥有者必须共同确认才能修改拥有者相关信息”,
————————————————————————————————————
假设有下述示例的一段ODIN标识注册信息:
{“title”:”PPk-ODIN-sample”,”email”:”ppkpub@gmail.com”,”auth”:”2″,”ap_list”:["http://ppkpub.org/AP/"]}
那么就可以按照上述的消息定义将其组装为一条比特币交易记录以广播到比特币网络上生效,对应交易的原始数据示例如下(将原始二进制数据按字节以16进制ASCII码形式输出,便于分析):
01000000032237b858f1a697cc2d26a451bd3fd3ef1944eb53f579b4fac38e5ecb5c0fc42c010000006b483045022100da55
a2d9f97695db12aecc0113662437957a6d4f17064ff49602ddc39904c31302201e81eae0c84f25019485ae4a2ce9b67c0e84
85599df87ab876b469e3cbbd24100121022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9ff
ffffff2ef89686bebf72bd31b8f27780223f7b5f448d0110b6fdda19595a073f42a301000000006b483045022100d49360fa
6bd45b92a068127db31c9cfd93af87543799968a5b076c2fea151f9b0220647900f5fc763f5a3eed13d382e13a3bddd15646
867b56f1be9d2629b2ccb4360121022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9ffffff
ffd704b1c1977cd50be182134b18fafaa16db1e917dfe4f93bcab1584aabf323d4010000006b483045022100fb88f75cae8a
ccfe969cd89afbca677ff78a4914f5f506d6e5d481baf484e9f2022039f560414ec5a778a19565f7fe9e51b6acf7b841b4ba
2188785a5bb6051d7d510121022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9ffffffff03
7d160000000000001976a91451a09d25106715f09a14cac6367c3f4f2408590d88ac7d16000000000000cf5121022e9f3129
2873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9212050325020697320667574757265212070706b7075
622e6f72672d3e70706b3a302120525400657b227469746c65223a2250506b2d4f44494e2d73616d706c65222c222120656d
61696c223a2270706b70756240676d61696c2e636f6d222c22617574682221203a2232222c2261705f6c697374223a5b2268
7474703a2f2f70706b7075622e6f210972672f41502f225d7d000000000000000000000000000000000000000000000056ae
e6cac223000000001976a914391ef5239da2a3904cda1fd995fb7c4377487ea988ac00000000
对上述报文按协议规则可按字段分解说明如下:
01000000 // 版本号,UINT32
03 // Tx输入数量,变长INT。0×03=3个输入。
/*** 接下来是第一组Input Tx ***/
2237b858f1a697cc2d26a451bd3fd3ef1944eb53f579b4fac38e5ecb5c0fc42c // Tx交易的Hash值,固定32字节
01000000 // 消费的Tx位于前向交易输出的第0个,UINT32,固定4字节
6b // 接下来对应签名数据的长度, 0x6b = 107字节 。
// 这107字节长度的签名,含有两个部分:私钥签名 + 公钥。
// 当这里的数值为00时,则表示为尚未经过签名的原始交易
48 // 对应私钥签名的数据长度,0×48 = 72字节
3045022100da55a2d9f97695db12aecc0113662437957a6d4f17064ff49602ddc39904c31302201e
81eae0c84f25019485ae4a2ce9b67c0e8485599df87ab876b469e3cbbd241001 //私钥签名内容
21 // 对应公钥的数据长度,0×21 = 33字节
022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9 //对应公钥数据
ffffffff // 序列号,UINT32, 固定4字节。该字段是目前未被使用的交易替换功能,默认都设成0xFFFFFFFF
/*** 第二组Input Tx。与上同理,省略分解 ***/
2ef89686bebf72bd31b8f27780223f7b5f448d0110b6fdda19595a073f42a301000000006b483045
022100d49360fa6bd45b92a068127db31c9cfd93af87543799968a5b076c2fea151f9b0220647900
f5fc763f5a3eed13d382e13a3bddd15646867b56f1be9d2629b2ccb4360121022e9f31292873eee4
95ca9744fc410343ff373622cca60d3a4c926e58716114b9ffffffff
/*** 第三组Input Tx。与上同理,省略分解 ***/
d704b1c1977cd50be182134b18fafaa16db1e917dfe4f93bcab1584aabf323d4010000006b483045
022100fb88f75cae8accfe969cd89afbca677ff78a4914f5f506d6e5d481baf484e9f2022039f560
414ec5a778a19565f7fe9e51b6acf7b841b4ba2188785a5bb6051d7d510121022e9f31292873eee4
95ca9744fc410343ff373622cca60d3a4c926e58716114b9ffffffff
03 // Tx输出交易数量,变长INT类型。0×03=3个输出。
/*** 第一组输出 ***/
7d16000000000000 //输出的比特币数量,UINT64,8个字节。字节序需翻转得到0x000000000000167d = 5757 satoshi = 0.00005757 BTC
19 //输出描述脚本字节数, 0×19 = 25字节,由一些操作码与数值构成
76 //脚本起始操作 0×76 代表 OP_DUP(复制栈顶元素)
a9 //地址类型 0xa9 代表 OP_HASH160(栈顶项进行两次HASH,先用SHA-256,再用RIPEMD-160)
14 //地址长度 0×14 = 20字节
51a09d25106715f09a14cac6367c3f4f2408590d //对应ODIN标识拥有者地址的HASH160值,20字节
88 //0×88 代表 OP_EQUALVERIFY (运行脚本的二进制算术和条件,如结果为0,之后运行OP_VERIFY)
ac //Oxac 代表 OP_CHECKSIG (交易所用的签名必须是哈希值和公钥的有效签名,如果为真,则返回1)
/*** 第二组输出 ***/
7d16000000000000 //输出的比特币数量,UINT64,8个字节。字节序需翻转,
cf //输出描述脚本字节数, 0xcf = 207字节,由一些操作码与数值构成
51 //Ox51代表OP_1(将脚本代码1压入堆栈)
21 //压入堆栈的第1个公钥的数据长度,0×21 = 33字节。对应ODIN标识注册者地址的公钥
022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9
21 //压入堆栈的第2个公钥的数据长度。从第2个公钥开始嵌入ODIN标识消息内容
2050325020697320667574757265212070706b7075622e6f72672d3e70706b3a30
21 //压入堆栈的第3个公钥的数据长度
20525400657b227469746c65223a2250506b2d4f44494e2d73616d706c65222c22
21 //压入堆栈的第4个公钥的数据长度
20656d61696c223a2270706b70756240676d61696c2e636f6d222c226175746822
21 //压入堆栈的第5个公钥的数据长度
203a2232222c2261705f6c697374223a5b22687474703a2f2f70706b7075622e6f
21 //压入堆栈的第6个公钥的数据长度
0972672f41502f225d7d0000000000000000000000000000000000000000000000
56 //Ox56 代表 OP_6 (将脚本代码6压入堆栈)。与前面的0×51合在一起表示1of6多重签名
ae //Oxae 代表 OP_CHECKMULTISIG (执行多重签名验证)
/*** 第三组输出 ***/
e6cac22300000000 //输出的比特币数量,UINT64,8个字节。字节序需翻转,
19 //输出描述脚本字节数, 0×19 = 25字节,由一些操作码与数值构成
76 //脚本起始操作 0×76 代表 OP_DUP(复制栈顶元素)
a9 //地址类型 0xa9 代表 OP_HASH160(栈顶项进行两次HASH,先用SHA-256,再用RIPEMD-160)
14 //地址长度 0×14 = 20字节
391ef5239da2a3904cda1fd995fb7c4377487ea9 // 对应的HASH160值,20字节
88 //0×88 代表 OP_EQUALVERIFY (运行脚本的二进制算术和条件,如结果为0,之后运行OP_VERIFY)
ac //Oxac 代表 OP_CHECKSIG (交易所用的签名必须是哈希值和公钥的有效签名,如果为真,则返回1)
00000000 // 锁定时间,UINT32,固定4字节
结合前文说说明的ODIN协议定义内容,通过上述交易数据中的蓝色区域部分就可以还原解析出对应ODIN标识注册消息。
三、运行示例程序
示例程序包括两部分:
1. OdinMonitorTestnet.js :
监测比特币测试网络Testnet相关区块链数据的变化,从中解析出新注册的ODIN标识。
源码可以从这里复制
//************************************************//
// ODIN sample based Bitcoin-Testnet of node.js //
// PPk Public Group @2016. //
// http://ppkpub.org //
// Released under the MIT License. //
//************************************************//
//对应比特币测试网络(Bitcoin testnet)的RPC服务接口访问参数
var RPC_USERNAME='admin1';
var RPC_PASSWORD='123';
var RPC_HOST="127.0.0.1";
var RPC_PORT=19001;
PPK_ODIN_PREFIX='P2P is future!ppkpub.org->ppk:0'; //ODIN协议定义的前缀标识
console.log('Hello, ODIN monitor sample based Bitcoin-Testnet.');
console.log(' PPk Public Group @2016 ');
//初始化访问RPC服务接口的对象
var client = require('kapitalize')();
client
.auth(RPC_USERNAME, RPC_PASSWORD)
.set('host', RPC_HOST)
.set({
port:RPC_PORT
});
//显示当前连接的比特币测试网络信息
client.getInfo(function(err, info) {
if (err) return console.log(err);
console.log('Info:', info);
});
//记录已解析过的区块高度
var parsedBlockHeight=0;
setInterval(function(){doOdinMonSample();},1000);
// ODIN开源项目示例实现功能
function doOdinMonSample(){
//获取区块链上已产生的区块总数
client.getblockcount(function(err1, block_count) {
if (err1) return console.log('ERROR[getblockcount]:',err1);
console.log('block_count:', block_count);
//对尚未解析过的新区块进行解析
for(var block_height=parsedBlockHeight+1;block_height<block_count;block_height++){
//获取指定区块HASH值
client.getblockhash(block_height,function(err2, block_hash) {
if (err2) return console.log('ERROR[getblockhash]:',err2);
parseBlock(block_hash);
});
parsedBlockHeight=block_height;
}
});
}
//解析指定区块数据
function parseBlock(block_hash){
//获取区块数据
client.getblock(block_hash,function(err1, block_info) {
if (err1) return console.log('ERROR[getblock]:',err1);
//console.log('block_info:', block_info);
var block_height=block_info.height;
var tx_list=block_info.tx;
console.log('Parsing block[',block_height,']:', block_hash);
//对尚未解析过的新区块进行解析
for(var kk=0;kk<tx_list.length;kk++){
parseTransaction(block_height,tx_list[kk]);
}
});
}
//解析指定区块数据
function parseTransaction(block_height,tx_hash){
//onsole.log('Parsing TX:', tx_hash);
//检查刚广播的交易是否已被矿工确认接受存入区块链
client.gettransaction(tx_hash,function(err1, tx_info){
if (err1) return console.log('ERROR[gettransaction]:',err1);
if (!err1){
//console.log('tx_info:', tx_info);
//console.log('TX had been confirmed by ',tx_info.confirmations,' blocks.');
if(tx_info.confirmations>0){
var tx_in_block_index=tx_info.blockindex;
var tx_hex=tx_info.hex;
//console.log('TX hex: ',tx_hex);
var odinRecord=parseOdinFromTxHex(tx_hex);
if(odinRecord!=null){
var full_odin_id=block_height+'.'+tx_in_block_index;
console.log('Found a new ODIN[',full_odin_id,"]:\n",odinRecord);
}
}
}
});
}
//从16进制表示的交易原始HEX字符串中提取出可能嵌入的ODIN标识注册信息
function parseOdinFromTxHex(tx_hex){
var out_list=getOutListFromTxHex(tx_hex);
//console.log('out_list:', out_list);
var ownerAddress='';
var registerAddress='';
var odinSet;
var strValidDataInScript='';
for(var kk=0;kk<out_list.length;kk++){
var script_hex=out_list[kk].script_hex;
var parsedRecord=parseScriptHex(script_hex);
if(parsedRecord!=null){
if( parsedRecord.opcode==0xAE){
if (registerAddress.length==0)
registerAddress = parsedRecord.pubkeys[0];
if(parsedRecord.embed_data.length>0)
strValidDataInScript+=parsedRecord.embed_data;
}
if (ownerAddress.length==0 && parsedRecord.opcode==0xAC) {
ownerAddress = parsedRecord.addr_hash160;
}
}
}
//console.log( 'strValidDataInScript=',strValidDataInScript );
var strPrefix=strValidDataInScript.substr(0,31);
if(strPrefix==PPK_ODIN_PREFIX){
var msgType=strValidDataInScript.substr(31,1);
if(msgType=='R'){
var msgFormat=strValidDataInScript.substr(32,1);
var msgLen=strValidDataInScript.charCodeAt(33)*256+strValidDataInScript.charCodeAt(34);
//console.log('msgType=',msgType,',msgFormat=',msgFormat,',msgLen=',msgLen);
if(msgFormat=='T') //Normal text
msgContent=strValidDataInScript.substr(35);
else if(msgFormat=='G') //Gzip compressed data
msgContent=gzuncompress(strValidDataInScript.substr(35));
//console.log( 'msgContent=',msgContent );
odinSet=eval('('+msgContent+')');
}
if (ownerAddress.length==0)
ownerAddress=registerAddress;
odinRecord={
'register_pubkey':registerAddress,
'owner_address_hash160':ownerAddress,
'setting':odinSet,
};
//console.log("Found ODIN:",odinRecord);
return odinRecord;
}
return null;
}
//从16进制表示的交易原始HEX字符串中提取出输出交易列表
function getOutListFromTxHex(tx_hex){
var from = 0;
var ver=tx_hex.substr(from,4*2);
from += 4*2;
//console.log('ver=',ver);
//获取输入数量,注意按变长INT类型处理
var in_num=parseInt(tx_hex.substr(from,1*2),'16');
from += 1*2;
if(in_num==0xFD){
in_num=parseInt(tx_hex.substr(from,2*2),'16');
from += 2*2;
}else if(in_num==0xFE){
in_num=parseInt(tx_hex.substr(from,4*2),'16');
from += 4*2;
}
//console.log('in_num=',in_num);
for(var kk=0;kk<in_num;kk++){
var in_tx_hash=tx_hex.substr(from,32*2);
from += 32*2;
var in_tx_vout=tx_hex.substr(from,4*2);
from += 4*2;
var sign_length=parseInt(tx_hex.substr(from,1*2),'16');
from += 1*2 + sign_length*2;
var sequence=tx_hex.substr(from,4*2);
from += 4*2;
//console.log('in_tx_hash=',in_tx_hash);
//console.log('in_tx_vout=',in_tx_vout);
//console.log('sign_length=',sign_length);
//console.log('sequence=',sequence);
}
//获取输出数量,注意按变长INT类型处理
var out_num=parseInt(tx_hex.substr(from,1*2),'16');
from += 1*2;
if(out_num==0xFD){
out_num=parseInt(tx_hex.substr(from,2*2),'16');
from += 2*2;
}else if(out_num==0xFE){
out_num=parseInt(tx_hex.substr(from,4*2),'16');
from += 4*2;
}
//console.log('out_num=',out_num);
var out_array=[];
for(var kk=0;kk<out_num;kk++){
var out_val=parseInt(reverseHex(tx_hex.substr(from,8*2)),'16')/100000000;
from += 8*2;
var script_length=parseInt(tx_hex.substr(from,1*2),'16');
from += 1*2;
var script_hex=tx_hex.substr(from,script_length*2);
from += script_length*2;
//console.log('out_val=',out_val);
//console.log('script_length=',script_length);
//console.log('script_hex=',script_hex);
out_array[kk]={'out_btc':out_val,'script_hex':script_hex};
}
var lock_time=tx_hex.substr(from,4*2);
from += 4*2;
//console.log('lock_time=',lock_time);
return out_array;
}
/*
从16进制表示的交易脚本script中提取出有效的内嵌数据
*/
function parseScriptHex(script_hex){
var parsed_record=null;
opcode1=parseInt(script_hex.substr(0,2),'16');
opcode2=parseInt(script_hex.substr(script_hex.length-4,2),'16');
opcode3=parseInt(script_hex.substr(script_hex.length-2,2),'16');
//console.log('opcode:',opcode1,',',opcode2,',',opcode3);
if(opcode1==0x51 && opcode3==0xAE){ //符合比特币协议的多重签名(MULTISIG)特征
var str_embed_data='';
var array_pubkeys=[];
var from=2;
pubkey_len=parseInt(script_hex.substr(from,1*2),'16');
from+=1*2;
pubkey_str=script_hex.substr(from,pubkey_len*2);
array_pubkeys[0]=pubkey_str;
from+=pubkey_len*2;
//console.log("pubkeyLen1=",pubkey_len,",pubkeyStr=",pubkey_str);
for(var pp=1;pp<opcode2-0x50;pp++){
pubkey_len=parseInt(script_hex.substr(from,1*2),'16');
from+=1*2;
pubkey_str=script_hex.substr(from,pubkey_len*2);
array_pubkeys[pp]=pubkey_str;
//console.log("pubkeyLen[",pp+1,"]=",pubkey_len,",pubkeyStr=",pubkey_str);
from+=pubkey_len*2;
valid_data_len=parseInt(pubkey_str.substr(2,2),'16');
temp_str=pubkey_str.substr(4,valid_data_len*2);
for(var kk=0;kk<valid_data_len*2;kk+=2)
str_embed_data += String.fromCharCode(parseInt(temp_str.substr(kk,2),'16'));
}
parsed_record={
'opcode':opcode3,
'pubkeys':array_pubkeys,
'embed_data':str_embed_data
};
}else if(opcode1==0x76 && opcode2==0x88 && opcode3==0xAC){ //符合比特币协议的普通转账交易(CHECKSIG)特征
var from=2;
pubkey_type=parseInt(script_hex.substr(from,1*2),'16');
from+=1*2;
pubkey_len=parseInt(script_hex.substr(from,1*2),'16');
from+=1*2;
pubkey_str=script_hex.substr(from,pubkey_len*2);
from+=pubkey_len*2;
//console.log("pubkeyType=",pubkey_type,"pubkeyLen=",pubkey_len,",pubkeyStr=",pubkey_str);
parsed_record={
'opcode':opcode3,
'addr_hash160':pubkey_str
};
}
//console.log('parsed_record=',parsed_record);
return parsed_record;
}
//将HEX字符串反序输出
function reverseHex(old){
var array_splited=old.match(/.{2}|.+$/g);
var reversed='';
for(var kk=array_splited.length-1;kk>=0;kk--){
reversed += array_splited[kk];
}
return reversed;
}
//解压gzip压缩数据返回原始内容字符串
function gzuncompress(compressed_data){
require('zlib');
return zlib.gzUncompress(compressed_data);
}
2. OdinRegisterTestnet.js :
在比特币测试网络Testnet注册一个新的ODIN标识。
源码可以从这里复制
//************************************************//
// ODIN sample based Bitcoin-Testnet of node.js //
// PPk Public Group @2016. //
// http://ppkpub.org //
// Released under the MIT License. //
//************************************************//
//对应比特币测试网络(Bitcoin testnet)的RPC服务接口访问参数
var RPC_USERNAME='admin1';
var RPC_PASSWORD='123';
var RPC_HOST="127.0.0.1";
var RPC_PORT=19001;
//测试使用的相关参数
TEST_REGISTER_ADDRESS='mkiytxYA6kxUC8iTnzLPgMfCphnz91zRfZ'; //测试用的注册者钱包地址,注意与比特币正式地址的区别
TEST_REGISTER_PUBKEY_HEX='022e9f31292873eee495ca9744fc410343ff373622cca60d3a4c926e58716114b9'; //16进制表示的注册者钱包公钥
TEST_REGISTER_HASH160='391ef5239da2a3904cda1fd995fb7c4377487ea9'; //HASH160格式的注册者钱包公钥
TEST_REGISTER_PRIVATE_KEY='cTAUfueRoL1HUXasWdnETANA7uRq33BUp3Sw88vKZpo9Hs8xWP82'; //测试用的注册者钱包私钥
TEST_REGISTER_WALLET_NAME='TestWallet1'; //测试的注册者钱包名称
TEST_OWNER_ADDRESS ='mnxZSEktpuDFeHd5NmKJzFsm6ReT3wzfXL'; //对应标识所有者的比特币地址
TEST_OWNER_HASH160 ='51a09d25106715f09a14cac6367c3f4f2408590d'; //HASH160格式的对应标识所有者的地址公钥
MAX_N = 10; //单个1ofN多重签名输出中最多允许的公钥数量N取值
MIN_UNSPENT_NUM = 2; //最少作为输入需要的未使用交易记录数量
MIN_DUST_AMOUNT = 5757; //最小有效交易金额,单位satoshi,即0.00000001 BTC
MIN_TRANSACTION_FEE = 20000; //矿工费用的最小金额,单位satoshi
PPK_ODIN_PREFIX='P2P is future!ppkpub.org->ppk:0'; //ODIN协议定义的前缀标识
console.log('Hello, ODIN register sample Bitcoin-Testnet.');
console.log(' PPk Public Group @2016 ');
//初始化访问RPC服务接口的对象
var client = require('kapitalize')()
client
.auth(RPC_USERNAME, RPC_PASSWORD)
.set('host', RPC_HOST)
.set({
port:RPC_PORT
});
//显示当前连接的比特币测试网络信息
client.getInfo(function(err, info) {
if (err) return console.log(err);
console.log('Info:', info);
});
//检查测试帐号是否已存在于测试节点
client.getaccount(TEST_REGISTER_ADDRESS,function(err, result) {
if (err || result!=TEST_REGISTER_WALLET_NAME ) { //如不存在,则新导入测试帐号私钥
console.log('Import the test account[',TEST_REGISTER_WALLET_NAME,']:',TEST_REGISTER_ADDRESS);
client.importprivkey(TEST_REGISTER_PRIVATE_KEY,TEST_REGISTER_WALLET_NAME,function(err, imported_result) {
if (err) return console.log(err);
console.log('Imported OK:', imported_result);
doOdinRegisterSample();
});
}else{ //如已存在,则直接执行示例
console.log('The test account[',TEST_REGISTER_WALLET_NAME,'] existed. Address:',TEST_REGISTER_ADDRESS);
doOdinRegisterSample();
}
});
//对应ODIN开源项目的注册功能示例实现
function doOdinRegisterSample(){
//获取未使用的交易用于生成新交易
client.listunspent(6,9999999,[TEST_REGISTER_ADDRESS],function(err2, array_unspent) {
if (err2) return console.log('ERROR[listunspent]:',err2);
console.log('Unspent:', array_unspent);
//组织ODIN注册信息数据块
var ODIN_SETTING_DEMO = '{"title":"PPk-ODIN-sample","email":"ppkpub@gmail.com","auth":"2","ap_list":["http://ppkpub.org/AP/"]}';
var ODIN_DATA_DEMO = PPK_ODIN_PREFIX+'RT'
+ String.fromCharCode(ODIN_SETTING_DEMO.length/256)+String.fromCharCode(ODIN_SETTING_DEMO.length%256)
+ ODIN_SETTING_DEMO; //测试用ODIN注册信息
console.log('ODIN_DATA_DEMO=',ODIN_DATA_DEMO);
//构建1ofN多重签名输出来嵌入自定义的ODIN标识注册数据(N取值由配置参数MAX_N决定)
var obj_multisig_txs = generateMultiSigTX(ODIN_DATA_DEMO);
var multisig_tx_num = obj_multisig_txs.tx_num;
var multisig_txs_hex = obj_multisig_txs.hex;
//生成所需输入交易定义块
var min_unspent_amount = MIN_DUST_AMOUNT*(multisig_tx_num+1)+MIN_TRANSACTION_FEE;
var array_transaction_in = [];
var sum_amount = 0;
for(var uu=0;uu<array_unspent.length;uu++){
var unspent_record=array_unspent[uu];
if(unspent_record.amount>0){
sum_amount += unspent_record.amount*100000000;
array_transaction_in[array_transaction_in.length]={"txid":unspent_record.txid,"vout":unspent_record.vout,"amount":unspent_record.amount};
if( sum_amount > min_unspent_amount && uu>=MIN_UNSPENT_NUM-1 ) //需要足够金额和数量的输入
break;
}
}
console.log('Transaction_in:', array_transaction_in);
//确保新交易的输入金额和记录数量满足最小交易条件
if (sum_amount<=min_unspent_amount) return console.log('Invalid unspent amount');
if (array_transaction_in.length<MIN_UNSPENT_NUM) return console.log('Unspent num should be more than ',MIN_UNSPENT_NUM);
//构建原始交易数据
var rawtransaction_hex = '01000000'; // Bitcoin协议版本号,UINT32
rawtransaction_hex += byteToHex(array_transaction_in.length) ; //声明输入交易数量
for(var kk=0;kk<array_transaction_in.length;kk++){
rawtransaction_hex += reverseHex(array_transaction_in[kk].txid)+uIntToHex(array_transaction_in[kk].vout);
rawtransaction_hex += "00ffffffff"; // 签名数据块的长度和序列号, 00表示尚未签名
}
rawtransaction_hex += byteToHex( multisig_tx_num +2) ; //声明输出交易数量
//首先添加一个输出交易声明标识所有者
rawtransaction_hex += uIntToHex(MIN_DUST_AMOUNT)+"00000000";
rawtransaction_hex += "1976a914" + TEST_OWNER_HASH160 +"88ac"; //输出地址对应标识拥有者的钱包地址
//添加多重交易输出内容
rawtransaction_hex += multisig_txs_hex;
//最后添加一个找零输出交易
var charge_amount = sum_amount - min_unspent_amount;
console.log('sum_amount:', sum_amount);
console.log('min_unspent_amount:', min_unspent_amount);
console.log('charge_amount:', charge_amount);
console.log('uIntToHex(',charge_amount,')=', uIntToHex(charge_amount));
rawtransaction_hex += uIntToHex(charge_amount)+"00000000"; //找零金额,UINT64
rawtransaction_hex += "1976a914" + TEST_REGISTER_HASH160 +"88ac"; //找零地址为发送者的钱包地址
rawtransaction_hex += "00000000"; //锁定时间,缺省设置成0,表示立即执行,是整个交易数据块的结束字段
console.log('Rawtransaction:', rawtransaction_hex);
//签名交易原始数据包
client.signrawtransaction(rawtransaction_hex,function(err3, signedtransaction) {
if (err3) return console.log('ERROR[signrawtransaction]:',err3);
console.log('Signedtransaction:', signedtransaction);
if (!signedtransaction.complete) return console.log('signrawtransaction failed');
var signedtransaction_hex_str=signedtransaction.hex;
console.log('signedtransaction_hex_str:', signedtransaction_hex_str);
//广播已签名的交易数据包
client.sendrawtransaction(signedtransaction_hex_str,false,function(err4, sended_tx){ //注意第二个参数缺省为false,如果设为true则指Allow high fees to force it to spend,会强制发送交易并将in与out金额差额部分作为矿工费用(谨慎!)
if (err4) return console.log('ERROR[sendrawtransaction]:',err4);
console.log('Sended TX:', sended_tx);
//等待新区块的产生,确认注册结果
console.log('Waiting for the pending TX to be confirmed by new blocks ...');
setTimeout(function(){checkRegisterResult(sended_tx);},5000); //等待5秒后检查结果
});
});
});
}
//将指定ODIN数据字符串构建为多重签名输出数据块
function generateMultiSigTX(str_odin_data){
//将原始字节字符串转换为用16进制表示
var str_odin_hex=stringToHex(str_odin_data);
console.log('str_odin_hex=',str_odin_hex);
//将16进制表示的ODIN数据块分割构建若干公钥
var array_splited=str_odin_hex.match(/.{62}|.+$/g); //注意这里用62而不是31,是因为转换为16进制表示的字符串长度变大为原始字节字符串的2倍
var array_pubkey_hex=[];
for(var kk=0;kk<array_splited.length;kk++){
var temp_pubkey_hex='03'+byteToHex(array_splited[kk].length/2)+array_splited[kk];
for(var pp=array_splited[kk].length;pp<62;pp=pp+2){ //对于长度不足的字符串用00补足
temp_pubkey_hex+='00';
}
array_pubkey_hex[kk]=temp_pubkey_hex;
}
console.log( "array_pubkey_hex:\n",array_pubkey_hex );
//构建1ofN多重签名输出来嵌入自定义的ODIN标识注册数据(N取值由配置参数MAX_N决定)
var multisig_tx_num=0;
var multisig_txs_hex="";
for(var multisig_tx_num=0;multisig_tx_num*(MAX_N-1)<array_pubkey_hex.length;multisig_tx_num++){
//多重签名输出的第一个公钥固定为标识注册者对应公钥
var tmp_script_hex = "51" + byteToHex(TEST_REGISTER_PUBKEY_HEX.length/2) + TEST_REGISTER_PUBKEY_HEX;
var kk=0;
for(kk=0;kk < MAX_N-1 && kk+multisig_tx_num*(MAX_N-1)<array_pubkey_hex.length;kk++){
var tmp_pubkey=array_pubkey_hex[kk+multisig_tx_num*(MAX_N-1)];
console.log('kk=',kk,',multisig_tx_num=', multisig_tx_num,',tmp_pubkey[',(kk+multisig_tx_num*(MAX_N-1)),']=',tmp_pubkey);
tmp_script_hex += "21" + tmp_pubkey;
}
tmp_script_hex += byteToHex(0x50+kk+1)+"ae" ;
console.log('tmp_script_hex[',multisig_tx_num,']:', tmp_script_hex);
multisig_txs_hex += uIntToHex(MIN_DUST_AMOUNT)+"00000000"; //交易金额,UINT64
console.log(' uIntToVarintHex(',tmp_script_hex.length/2,') = ',uIntToVarintHex(tmp_script_hex.length/2));
multisig_txs_hex += uIntToVarintHex(tmp_script_hex.length/2)+tmp_script_hex;
}
return {'tx_num':multisig_tx_num,'hex':multisig_txs_hex};
}
//查询确认注册结果
function checkRegisterResult(sended_tx){
//查询刚广播的交易是否已被矿工确认接受存入区块链
client.gettransaction(sended_tx,function(err1, tx_info){
if (err1) return console.log('ERROR[gettransaction]:',err1);
//console.log('tx_info:', tx_info);
if(tx_info.confirmations>0){ //已经被区块链确认收录
console.log('TX had been confirmed by ',tx_info.confirmations,' blocks.');
var confirmed_block_hash=tx_info.blockhash;
var block_index=tx_info.blockindex;
client.getblock(confirmed_block_hash,function(err2, block_info){
if (err2) return console.log('ERROR[getblock]:',err2);
//console.log('block_info:', block_info);
var block_height=block_info.height;
console.log('New ODIN is ',block_height+'.'+block_index);
process.exit();
});
} else { //尚未被确认收录,则继续等待下一次检查
console.log('TX not been confirmed. Waiting...');
setTimeout(function(){checkRegisterResult(sended_tx);},5000);
}
});
}
//1字节整数转换成16进制字符串
function byteToHex(val){
var resultStr='';
var tmpstr=parseInt(val%256).toString(16);
resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;
return resultStr;
}
//32位无符号整数转换成VARINT格式的16进制字符串
function uIntToVarintHex(val){
var resultStr='';
if(val<0xFD){
resultStr = byteToHex(val);
}else if(val<0xFFFF ){
resultStr = 'FD'+byteToHex(val%256)+byteToHex(val/256)
}else if(val<0xFFFFFFFF ){
resultStr = 'FE'+uIntToHex(val);
}
return resultStr;
}
//将HEX字符串反序输出
function reverseHex(old){
var array_splited=old.match(/.{2}|.+$/g);
var reversed='';
for(var kk=array_splited.length-1;kk>=0;kk--){
reversed += array_splited[kk];
}
return reversed;
}
//32位无符号整数变成16进制,并按翻转字节序
function uIntToHex(val){
var resultStr='';
var tmpstr=parseInt(val%256).toString(16);
resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;
tmpstr=parseInt((val%65536)/256).toString(16);
resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;
tmpstr=parseInt(parseInt(val/65536)%256).toString(16);
resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;
tmpstr=parseInt(parseInt(val/65536)/256).toString(16);
resultStr += tmpstr.length==1? '0'+tmpstr : tmpstr;
return resultStr;
}
//Ascii/Unicode字符串转换成16进制表示
function stringToHex(str){
var val="";
for(var i = 0; i < str.length; i++){
var tmpstr=str.charCodeAt(i).toString(16); //Unicode
val += tmpstr.length==1? '0'+tmpstr : tmpstr;
}
return val;
}
将上述示例代码下载保存到已安装Node.js的测试环境下(保存文件名为 OdinRegisterTestnet.js和OdinMonitorTestnet.js)。
关于如何安装Node.js测试环境, 如有需要可以参考《区块链开发由浅入深指南一》里面的说明进行安装( https://www.shikexu.com/2018/04/20/%E6%AF%94%E7%89%B9%E5%B8%81%E5%8C%BA%E5%9D%97%E9%93%BE%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97%EF%BC%88%E4%B8%80%EF%BC%89/)。
在运行上述示例程序前,请确认已安装比特币测试网络(bitcoin-testnet)的Docker运行环境。如尚未安装可以参考《区块链开发由浅入深指南二》里面的说明进行安装( https://www.shikexu.com/2018/04/20/%E6%AF%94%E7%89%B9%E5%B8%81%E5%8C%BA%E5%9D%97%E9%93%BE%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97%EF%BC%88%E4%BA%8C%EF%BC%89/ )。
在Node.js开发测试环境新开一个文本终端窗口,在命令行下输入以下命令启动监测示例程序:
node OdinMonitorTestnet.js
然后新开一个文本终端窗口,在命令行下输入以下命令运行注册示例程序:
node OdinRegisterTestnet.js
运行注册示例程序后,到比特币测试网络(bitcoin-testnet)的Docker运行环境的命令行下输入”make generate BLOCKS=6″, 模拟产生新的区块记录,刚产生的交易记录就会得到有效的确认并被存入区块链中。 这时在监测程序的运行界面上就会提示解析到新的ODIN标识注册记录(如下图所示)。
- 我的微信
- 这是我的微信扫一扫
-
- 我的微信公众号
- 我的微信公众号扫一扫
-