o我正试着按照bip143中的描述做我自己的证人签名。我有正确验证示例签名的代码,所以我认为我掌握了基本的签名和验证。我有代码可以正确地验证bip143中的签名,并且使用相同的代码可以正确地验证这个输出,所以我不确定如何确定出了什么问题。我得到的错误是
错误代码:-26
错误消息:
非强制脚本验证标志(非规范DER签名)
我怀疑我并没有改变某些东西的字节顺序,但我真的不知道如何进一步诊断这个问题。
—-未签名事务——-
0200000000102303E285BB228344346ACB5292397A7610103EFCD0182C4CF95B4D01B2BDAD400000000000FFFFFF02400D030000000000016001453F0B90D7BE69D4F5F1356908A9E12EF93561DF74E0022A0000000160140F54E16BD59DA94840741B69AB9DB99467A8580000000000
——-结束未签名事务——
dhash(prevouts)=02303E285BB228344346ACB5292397A761103EFCD0182C4CF95B4D01B2BDAD4000000000
dhash(序列)=FFFFFF
dhash(输出)=400D0300000000000001453F0B90D7BE69D4F5F1356908A9E12FEF93561DF74E0022A01000000140F54E16BD59DA94840741B69AB9B9DB99467A858
版本:02000000
哈希输出:9ebc8589966e2dd13cd64af1835262c2d8e931b4388a8ebe76620814b4f407bd
哈希序列:3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044
输出点:02303E285BB228344346ACB5292397A761103EFCD0182C4CF95B4D01B2BDAD4000000000
脚本代码:1976a914992fda25fdf8447092aa223a34bedd3572173d8688ac
金额:00f2052a01000000
N序列:FFFFFF
哈希输出a54e6a8744fa773a5f1c64d7f60d53efd69f8d76e85e7817970a87990874e4c1
n锁定时间00000000
Nhash01000000型
预哈希图像:0200000009EBC8589966E2DD13CD64AF1835262C2D8E931B4388A8EBE76620814B4F407BD3BB13029CE7B1F559EF5E747FCAC439F1455A2EC7C5F09B72290795E7066504402303E285BB228344346ACB52397A7610103EFCD0182C4CF95B4D01BDAD40000000001976A914992FDA25FDF8447092AA223A34BEDD572173D8688AC00F2052A010000FFFFFFFFA54E6A8744FA773A5F1C60D53EFD69E78179A990874E4C10000000001000000
dhashed图像:4f8fce636bb4a44aa6a241e9597bbe54fce3bfe3426a49a94c5b006d42eac007
图纸编号:2cbb748d06a1b87c933a3118d8c2a5a0ab02111 bae777a9e52ba8571eee2d549e80a6e4cef24a172e64d11aa7b83c8005fe6fc078307b9e663ef9eb14fce05a3
订单号:304402202CBB748D06A1B87C933A3118D8C2A5A0AB02111BAE777A9E52BA8571EEE2D5490220E80A6E4CEF24A172E64D1AA7B83C8005FE6FC078307B9E663EF9EB14FCE05A3
脚本发布键:992fda25fdf8447092aa223a34bedd3572173d86
出版编号:0392ff36d0ae9f3a74c0483fd309ff9144972b1dce6d6dfe4d9de474c721b36521
—-已签署交易——-
0200000000000102303E285BB228344346ACB5292397A7610103EFCD0182C4CF95B4D01B2BDAD400000000000FFFFFF02400D030000000000016001453F0B90D7BE69D4F5F1356908A9E12EF93561DF74E0022A0000000160140F54E16BD59DA94840741B69AB9DB99467A8580247304402202CBB748D06A1B87C933A3118D8C2A5A002111BAE777A9E52BA8571EEE2D5490220E80E4CEF1172AE7B83C8005FE078307B9E663EF9EB14FCE05A301210392FF36D0AE9F3A74C0483FD309FF9144972B1DCE6DFE4D9DE474C721B36520000000
——-结束已签名事务——
这是在我的regtest上运行的。我可以提供人们可能认为有用的任何其他信息。我花了很多时间比较每一个可能的例子之前,所有这里张贴。我知道它说的是非共通的der sig,但是我的研究,其他人说错误发生在很多不同的事情出错的时候。另外,当我将签名与核心签名进行比较时,它们在格式上与“30440220”、32字节、0220和其他32字节完全匹配。唯一不同的是实际的sig数据。另外,在使用本机connical DER输出时也会出现相同的错误。一如既往,我们非常感谢您的帮助
—–更新—–
因此,上面的脚本的问题主要是没有正确地进行DER编码,这意味着如果s或r most MSB>0x80,它会将其视为负数,因为它是一个有符号整数。在本例中,S的MSB是0xE8,大于0x80。比特币有一个策略,它实际上必须小于ecdsa曲线值“n”。下面首先是纠正问题的代码,我可以验证签名(但错误仍然存在于比特币核心中)。在python中反转S的代码:
n=11579208923731619542357098500868790785283756427907404038260516314181494337
s=字节数组(s\U字节)
辛特=0
cnt=31
对于s中的bb:
sint+=bb<<cnt*8
cnt-=1
如果sint>=n/2:
打印(“否定”)
sint=n-sint
s=字节数组(sint.to\字节(32,byteorder=’big’))
还不错。至于r,比特币核心只是重新签名,直到它低于0x80。如果der编码器正确地填充了MSB,则其值更高是合法的。我也是这么做的。下面是一个已更正问题的新事务,尽管我仍然得到相同的错误:-/。
—-未签名事务——-
0200000000102303E285BB228344346ACB5292397A761103EFCD0182C4CF95B4D01B2BDAD400000000000FFFFFF02400D030000000000016001453F0B90D7BE69D4F5F1356908A9E12EF93561DF74E0022A00000001601
hat不是有效的DER编码。当顶部(第一个)字节>=0x80时,表示的数字被解释为负数。当编码的值大于2^255时,意味着必须在前面再加一个0字节。比特币网络实际上还有一个额外的限制,要求签名中的S值低于n/2。这非常简单:当S>=n/2时,取反(替换为n-S)Pieter Wuille 6月22日1:08
抱歉,我不得不试一下。好的,所以当我使用本机规范DER格式化时,它有时会有前导0,并且长了一个。为了确保我能理解,基本上,当它大于0x80,而不是仅仅加上00,我们就否定了整个S?如果s[0]>0x80,s=0xFFFFFFFF…-s?-6月22日下午1:30
@我试过我认为正确的方法。还没有成功。当MSB为1时求S的反,并且不添加前导零,现在通常不会验证。比特币核心如何知道你是否反转了它6月22日下午2:01
这样做的原因是可塑性:我们不希望第三方中继交易,以改变它,使它txid改变,但不使它无效。结果表明,对于ECDSA,如果(r,s)是有效的签名,那么(r,n-s)也是有效的签名。为了避免这种影响,比特币核心有一个政策规则,即它只中继s值小于n/2的签名的交易。它不需要知道它是否被否定,因为这两种形式都是同等有效的ECDSAPieter Wuille 6月22日3:11
1
注意,n是一个非常重要的数字,大约是2^256-1.5*2^128(搜索secp256k1曲线顺序),R和s用大端表示法编码Pieter Wuille 6月22日3:12
再显示7条评论
1个答案
0
好吧,我想出来了!大约一周后:-D。我想发布这篇文章是因为我花了上周的时间学习比特币核心,以便在sighash期间输出间歇值进行比较。任何其他需要真正了解核心工作原理的人,希望这会非常有帮助。首先,我的具体问题是:我的输出脚本完全不正确,我想我把锁定脚本和解锁脚本弄混了,这仍然不应该使签名无效,但是你也注意到在这个例子中,输出是这样的:
202CB206000000001976A9148280B37DF378DB99F66F85C95A783A76AC7A6D5988AC
第一个0x19不是脚本公钥的一部分(decode raw transaction显示它的方式)。所以在描述中
如果sighash类型既不是SINGLE也不是NONE,则hashOutputs是使用scriptPubKey(在CTxOuts中序列化为脚本)序列化所有输出量(8字节小endian)的双SHA256;
使用长度前缀0x19进行哈希运算。我在整个事务中包含了长度,所以它仍然可以很好地解码,但不是以我散列的方式。简单的误会。
下面是我如何构建和修改核心(0.21.1)以输出值来解决这个问题
我主要遵循这个例子https://jonatack.github.io/articles/how-to-compile-bitcoin-core-and-run-the-tests,基本上安装了该列表中的大多数依赖项
export BDB_PREFIX=’/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/db4′
./configure BDB\u LIBS=“-L${BDB\u PREFIX}/lib-ldb\u cxx-4.8”BDB\u CFLAGS=“-I${BDB\u PREFIX}/include”–启用调试
制作
注意–enable debug的用法。我认为这是必要的,让你使用gdb?我偶尔会在日志文件中遇到线程锁定错误,这会导致它在使用wallet api时在调试模式下崩溃。
我创建了一个单独的bin文件夹,其中包含一些指向4个主要二进制可执行文件的sym链接。然后编辑/etc/environment将路径附加到该文件夹。然后运行“source/etc/environment”。这使您可以从任何地方运行二进制文件。
现在可以运行bitcoind-regtest-daemon了。从这里您可以进入构建和运行gdb二进制文件的源代码,并附加到进程。
Modify<bitcoin\u dir>/src/script.interpreter.cpp在顶部,我编写了一些基本函数来获取一些不同的变量类型并打印为十六进制。抱歉,我是C人,不是C++。我创建了一个单独的日志文件,这样就不会弄乱bitcoind的主日志
td::字符串hexhalftostr(unsigned char val)
{
if(val<=9)
返回std::to\字符串(val);
else if(val==10)
返回“A”;
else if(val==11)
返回“B”;
else if(val==12)
返回“C”;
else if(val==13)
返回“D”;
else if(val==14)
返回“E”;
else if(val==15)
返回“F”;
否则返回“”;
}
void logbytes(无符号字符*数据,int len)
{
std::流myfile;
myfile.open(“/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/special.log”,std::ios\u base::app);
std::string ss=“”;
对于(int i=0;我<len;i++)
{
字符lsb,msb;
lsb=0x0f&数据[i];
msb=0x0f&(数据[i]<<4);
ss+=hexhalftostr(msb)+hexhalftostr(lsb);
}
我的文件<<ss;
myfile.close();
}
void logbigint(uint256数据)
{
logbytes(data.data(),32);
}
void logint(无符号int数据)
{
无符号字符数组字节[4];
对于(int i=0;i<4;i++)
arrayOfByte[i]=(数据<<(i*8));
对数字节(arrayOfByte,4);
}
void loglong(无符号长数据)
{
无符号字符数组字节[8];
对于(int i=0;i<8;i++)
arrayOfByte[i]=(数据<<(i*8));
对数字节(arrayOfByte,8);
}
void logtext(std::string ss)
{
std::流myfile;
myfile.open(“/home/ubuntu/btc/bitcoinsource/bitcoin-0.21.1/special.log”,std::ios\u base::app);
我的文件<<ss;
myfile.close();
}
SignatureHash是一个函数,我们可以在计算它们时实际输出它们
日志文本(“\n版本:”);
登录(txTo.version);
日志文本(“\nHashRevOuts:”);
logbigint(hashPrevouts);
logtext(“\nHashQuence:”);
logbigint(哈希序列);
日志文本(“\n点:”);
logbigint(txTo.vin[nIn].prevout.hash);
登录(txTo.vin[nIn].prevout.n);
//不知道如何获取字节
////日志文本(“\nscriptCode:”);
////logbigint(脚本代码);
日志文本(“\n数量:”);
loglong(数量);
日志文本(“\n序列:”);
登录(txTo.vin[nIn].n序列);
日志文本(“\nHaoutputs:”);
logbigint(哈希输出);
logtext(“\nLockTime:”);
登录(txTo.nLockTime);
日志文本(“\nhashType:”);
登录(nHashType);
函数的返回必须被替换,因为sha256散列会消耗数据
return ss.GetHash();
变成
uint256 sighash=ss.GetHash();
日志文本(“\n隐藏图像:”);
logbigint(sighash);
日志文本(“\n”);
//return ss.GetHash();
返灰;
我建议设置一个断点并逐步执行“EvalScript”函数。这是所有魔术发生的地方,并且对于教育自己实际比特币脚本如何运行是非常好的,因为每个错误代码包含几乎无限的错误,我不确定有没有其他方法来调试你的代码而不这样做。
分享
改进这个答案
跟随