Что именно хэшируется и подписывается в segwit-транзакции

У меня есть неподписанная необработанная транзакция segwit в моей regtestсети:

02000000011333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e370000000000ffffffff0200e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787f0c1a4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf02878700000000

Красивый формат:

Version: 02000000
Flag:
Input Count: 01
Input 1 Previous Output Hash: 1333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e37
Input 1 Previous Output Index: 00000000
Input 1 script length: 00 (empty for now)
Input 1 scriptSig: 
Input 1 sequence: feffffff
Output Count: 02
Output 1 Value: 00e1f50500000000 (100M satoshis / 1 BTC)
Output 1 public key script length: 0x17 (23 bytes)
Output 1 public key script: a914a860f76561c85551594c18eecceffaee8c4822d787
Output 2 Value: F0C1A43500000000 (899990000 sats / 8.9999 BTC)
Output 2 public key script length: 0x17 (23 bytes)
Output 2 public key script: a914d8b6fcc85a383261df05423ddf068a8987bf028787
Witness Count:
Witness 1 length:
Witness 1: 
Witness 2 length:
Witness 2: 
Locktime: 8c000000 (block 140)

Я могу подписать его, используя приведенные выше данные (объединенные сверху вниз):

$ bitcoin-cli -regtest signrawtransaction 02000000011333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e370000000000feffffff0200e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787F0C1A4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287878c000000

{
  "hex": "020000000001011333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e370000000017160014b93f973eb2bf0b614bddc0f47286788c98c535b4feffffff0200e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787f0c1a4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf028787024730440220434caf5bb442cb6a251e8bce0ec493f9a1a9c4423bcfc029e542b0e8a89d1b3f022011090d4e98f79c62b188245a4aa4eb77e912bfd57e0a9b9a1c5e65f2b39f3ab401210223bec70d670d29a30d9bcee197910e37cf2a10f0dc3c5ac44d865aec0d7052fb8c000000",
  "complete": true
}

Но когда я пытаюсь сделать это вручную, я получаю другой результат для подписи в свидетеле. Я думаю, что могу подписывать неправильные данные, так как же будет выглядеть сериализованная шестнадцатеричная строка, которая хешируется ( HASH256), а затем подписывается для создания подписи в данных-свидетелях ( свидетель 1 ниже)?

Окончательная передача (подписано с использованием signrawtransaction)

Version: 02000000
Flag: 0001 (indicates segwit)
Input Count: 01
Input 1 Previous Output Hash: 1333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e37
Input 1 Previous Output Index: 00000000
Input 1 script length: 0x17 (23 bytes)
Input 1 scriptSig: 160014b93f973eb2bf0b614bddc0f47286788c98c535b4
Input 1 sequence: feffffff
Output Count: 02
Output 1 Value: 00e1f50500000000 (100M satoshis / 1 BTC)
Output 1 public key script length: 0x17 (23 bytes)
Output 1 public key script: a914a860f76561c85551594c18eecceffaee8c4822d787
Output 2 Value: F0C1A43500000000 (899990000 sats / 8.9999 BTC)
Output 2 public key script length: 0x17 (23 bytes)
Output 2 public key script: a914d8b6fcc85a383261df05423ddf068a8987bf028787
Witness Count: 02
Witness 1 length: 0x47 (71 bytes)
Witness 1: 30440220434caf5bb442cb6a251e8bce0ec493f9a1a9c4423bcfc029e542b0e8a89d1b3f022011090d4e98f79c62b188245a4aa4eb77e912bfd57e0a9b9a1c5e65f2b39f3ab401
Witness 2 length: 0x21 (33 bytes)
Witness 2: 0223bec70d670d29a30d9bcee197910e37cf2a10f0dc3c5ac44d865aec0d7052fb
Locktime: 8c000000 (block 140)

Изменить : Код для подписи: GIST - ec_sign_hex.sh . В качестве параметров принимает окончательную хешированную шестнадцатеричную строку и 32-байтовый шестнадцатеричный закрытый ключ. Я использую закрытый ключ 72b90220501a0c27a499535c010de9e2fa190c0d287a940a3886bcb2cbcccdb8, и он выполняется как:

$ ec_sign_hex 02000000011333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e370000000000feffffff0200e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787F0C1A4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf0287878c000000 72b90220501a0c27a499535c010de9e2fa190c0d287a940a3886bcb2cbcccdb8

#!/bin/bash
## Command Line parsing
#######################

if [[ $# -lt 2 ]]; then
    echo "Usage: $ ec_sign_hex <input-hex> <priv-key-hex>"
    exit 1
fi

inputHex=$1
privKeyHex=$2

## Create .pem and .pub files
#############################
pubKeyHex="$(openssl ec -inform DER -text -noout -in <(cat <(echo -n "302e0201010420") <(echo -n "${privKeyHex}") <(echo -n "a00706052b8104000a") | xxd -r -p) 2>/dev/null | tail -6 | head -5 | sed 's/[ :]//g' | tr -d '\n')"
asnFormatKey="30740201010420${privKeyHex}a00706052b8104000aa144034200${pubKeyHex}"
echo "-----BEGIN EC PRIVATE KEY-----" > tmp.pem
echo $asnFormatKey | xxd -r -p | base64 | fold -w 64 >> tmp.pem
echo "-----END EC PRIVATE KEY-----" >> tmp.pem

openssl ec -in tmp.pem -pubout -out tmpPub.pem &>/dev/null

## Sign message
#  sign:
    openssl pkeyutl -sign -inkey tmp.pem -in <(printf $inputHex | xxd -r -p) -out tmp.sig
    echo "Signature"
    echo "####################"
    echo ""
    openssl pkeyutl -sign -inkey tmp.pem -in <(printf $inputHex | xxd -r -p) | xxd -p #-hexdump #| xxd -p
    echo ""
    echo "####################"
#  verify:
    openssl pkeyutl -verify -pubin -inkey tmpPub.pem -sigfile tmp.sig -in <(printf $inputHex | xxd -r -p)

rm tmp.pem tmpPub.pem tmp.sig

Обновление : я считаю, что приведенный выше скрипт не работает, потому что pkeyutlпо умолчанию перед подписанием используется отпечаток пальца sha1.

Вы следуете BIP143? Он имеет ряд примеров.
Было бы полезно, если бы вы могли опубликовать код, который вы используете для подписи.
@NateEldredge Только что добавил ссылку.
Немного связанно, знаете ли вы, где я могу найти бит, который определяет исходную генерацию хэша для p2pkh utxos? Не СегВит.

Ответы (1)

Разобрался с несколькими вещами:

Подписи SegWit

Для segwit-транзакций необходимо подписать следующее (см. BIP 143) :

Double SHA256 of the serialization of:
     1. nVersion of the transaction (4-byte little endian)
     2. hashPrevouts (32-byte hash)
     3. hashSequence (32-byte hash)
     4. outpoint (32-byte hash + 4-byte little endian) 
     5. scriptCode of the input (serialized as scripts inside CTxOuts)
     6. value of the output spent by this input (8-byte little endian)
     7. nSequence of the input (4-byte little endian)
     8. hashOutputs (32-byte hash)
     9. nLocktime of the transaction (4-byte little endian)
    10. sighash type of the signature (4-byte little endian)

Для приведенного выше примера P2SH-P2WPKH это будет:

hash prevouts - hash256(1333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e3700000000)
6623eab09650c6a6a98617d581c5d1fc26c6f5f158820e68ee636be93b433cee

hashSequence - hash256(feffffff)
18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198

hashOutputs - hash256(00e1f5050000000017a914a860f76561c85551594c18eecceffaee8c4822d787F0C1A4350000000017a914d8b6fcc85a383261df05423ddf068a8987bf028787)
6a7522acd2fc865421d71893d71027bc3ebe0839d25dabc6277aef64b24b2370

hashPreimage
020000006623eab09650c6a6a98617d581c5d1fc26c6f5f158820e68ee636be93b433cee18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe1981333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e37000000001976a914b93f973eb2bf0b614bddc0f47286788c98c535b488ac00ca9a3b00000000feffffff6a7522acd2fc865421d71893d71027bc3ebe0839d25dabc6277aef64b24b23708c00000001000000

nVersion:     02000000
hashPrevouts: 6623eab09650c6a6a98617d581c5d1fc26c6f5f158820e68ee636be93b433cee
hashSequence: 18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198
outpoint:     1333183ddf384da83ed49296136c70d206ad2b19331bf25d390e69b222165e3700000000
scriptCode:   (OP_DUP OP_HASH160 <pubkey hash> OP_EQUALVERIFY OP_CHECKSIG) where <pubkey hash> is from redeemscript from previous output
              1976a914b93f973eb2bf0b614bddc0f47286788c98c535b488ac
amount:       00ca9a3b00000000 (where amount is the total utxo amount)
nSequence:    feffffff
hashOutputs:  6a7522acd2fc865421d71893d71027bc3ebe0839d25dabc6277aef64b24b2370
nLockTime:    8c000000
nHashType:    01000000 (SIGHASH_ALL)

HashPreimage дважды хешируется sha256 для получения:

ef7d163bfe970439476d15537e1373bc23bb6282b9e145115331975a1a673788, который является данными, которые должны быть подписаны. В результате получается подпись: 30440220434caf5bb442cb6a251e8bce0ec493f9a1a9c4423bcfc029e542b0e8a89d1b3f022011090d4e98f79c62b188245a4aa4eb77e912bfd57e0a9b9a1c5e65f2b39f3ab401.

Скрипт подписи

Однако приведенный выше сценарий подписи никогда не будет работать , потому что opensslcli использует случайный одноразовый номер каждый раз, когда вы подписываете, см . https://bitcoin.stackexchange.com/a/32962/60443 . Поэтому я написал небольшой инструмент cli, используя libsecp256k1используемый в биткойнах. Компилируется с использованием:

g++ -std=c++11 -o ec_sign ec_sign.cpp $(pkg-config --cflags libsecp256k1 --libs libsecp256k1)

Применение:ec_sign <hash-to-sign> <priv-key> <hash-type>(default 1: SIGHASH_ALL)

#include <secp256k1.h>
#include <iostream>
#include <iomanip>

int char2int(char input)
{
  if(input >= '0' && input <= '9')
    return input - '0';
  if(input >= 'A' && input <= 'F')
    return input - 'A' + 10;
  if(input >= 'a' && input <= 'f')
    return input - 'a' + 10;
  throw std::invalid_argument("Invalid input string");
}

void hex2bin(const char* src, char* target)
{
  while(*src && src[1])
  {
    *(target++) = char2int(*src)*16 + char2int(src[1]);
    src += 2;
  }
}

// EC sign/verify
// ----------------------------------------------------------------------------

int main(int argc, char *argv[])
{
    uint8_t hashType = 1;
    if(argc < 3)
    {
        std::cerr << "Usage: ec_sign <hash-to-sign> <priv-key> <hash-type>(default 1: SIGHASH_ALL)" << std::endl;
        return 1;
    }
    if(argc == 4)
    {
        hashType = argv[3][0] - '0';
    }
    char hash[32];
    hex2bin(argv[1], hash);
    char secret[32];
    hex2bin(argv[2], secret);

    size_t siglen = 71;
    secp256k1_ecdsa_signature signature;
    static secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);

    unsigned char derSig[71];

    if (secp256k1_ecdsa_sign(ctx, &signature, (const unsigned char*)hash, (const unsigned char*)secret, secp256k1_nonce_function_rfc6979, NULL) != 1)
    {
        std::cerr << "Signature failed" << std::endl;
        return 1;
    }

    if (secp256k1_ecdsa_signature_serialize_der(ctx, derSig, &siglen, &signature) != 1)
    {
        std::cerr << "Serialization failed" << std::endl;
        return 1;
    }

    // Apply Hash type
    derSig[70] = hashType;

    for(int i=0; i<71; i++)
    {
        std::cout << std::hex << std::setfill('0') << std::setw(2) << (int)derSig[i];
    }
    std::cout << std::endl;

    return 0;
}
Использование случайных одноразовых номеров совершенно законно. В сети его не видно.
Большое спасибо @JBaczuk!! Знаете ли вы, почему они используют название «outpoint» вместо «outputs»? В BIP github.com/bitcoin/bips/blob/master/… они сказали «то же значение, что и исходный алгоритм», но для меня слово «недостаток» совершенно новое!
@aqquadro Термин outpoint является сокращением для комбинации хэша транзакции (prevHash) и ее индекса (vout).