ZIP 244 signature digests

This commit is contained in:
Jack Grigg 2021-06-12 16:40:16 +01:00
parent bd1fd2eaca
commit 0b8a348c2b
5 changed files with 284 additions and 11 deletions

View File

@ -12,6 +12,9 @@
extern "C" {
#endif
struct PrecomputedTxParts;
typedef struct PrecomputedTxParts PrecomputedTxParts;
/// Calculates identifying and authorizing digests for the given transaction.
///
/// If either `txid_ret` or `authDigest_ret` is `nullptr`, the corresponding
@ -24,6 +27,33 @@ bool zcash_transaction_digests(
unsigned char* txid_ret,
unsigned char* authDigest_ret);
/// Precomputes data for calculating signature digests from the given
/// transaction.
///
/// Please free this with `zcash_transaction_precomputed_free` when you are
/// done.
///
/// Returns `nullptr` if the transaction is invalid, or a v1-v4 transaction format.
PrecomputedTxParts* zcash_transaction_precomputed_init(
const unsigned char* txBytes,
size_t txBytes_len);
/// Frees a precomputed transaction from `zcash_transaction_precomputed_init`.
void zcash_transaction_precomputed_free(PrecomputedTxParts* preTx);
/// Calculates a signature digest for the given transparent input.
///
/// Returns `false` if any of the parameters are invalid; in this case,
/// `sighash_ret` will be unaltered.
bool zcash_transaction_transparent_signature_digest(
const PrecomputedTxParts* preTx,
uint32_t sighashType,
size_t index,
const unsigned char* scriptCode,
size_t scriptCode_len,
int64_t value,
unsigned char* sighash_ret);
#ifdef __cplusplus
}
#endif

View File

@ -1,10 +1,17 @@
use std::slice;
use std::{ptr, slice};
use blake2b_simd::Hash;
use libc::{c_uchar, size_t};
use tracing::error;
use zcash_primitives::{
consensus::BranchId,
transaction::{Transaction, TxVersion},
legacy::Script,
transaction::{
components::Amount,
sighash::{signature_hash, SignableInput},
txid::TxIdDigester,
Transaction, TxDigests, TxVersion,
},
};
/// Calculates identifying and authorizing digests for the given transaction.
@ -45,3 +52,102 @@ pub extern "C" fn zcash_transaction_digests(
true
}
pub struct PrecomputedTxParts {
tx: Transaction,
txid_parts: TxDigests<Hash>,
}
/// Precomputes the `TxDigest` struct for the given transaction.
///
/// Returns `nullptr` if the transaction is invalid, or a v1-v4 transaction format.
#[no_mangle]
pub extern "C" fn zcash_transaction_precomputed_init(
tx_bytes: *const c_uchar,
tx_bytes_len: size_t,
) -> *mut PrecomputedTxParts {
let tx_bytes = unsafe { slice::from_raw_parts(tx_bytes, tx_bytes_len) };
// We use a placeholder branch ID here, since it is not used for anything.
let tx = match Transaction::read(tx_bytes, BranchId::Canopy) {
Ok(tx) => tx,
Err(e) => {
error!("Failed to parse transaction: {}", e);
return ptr::null_mut();
}
};
match tx.version() {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
// We don't support these legacy transaction formats in this API.
ptr::null_mut()
}
_ => {
let txid_parts = tx.digest(TxIdDigester);
Box::into_raw(Box::new(PrecomputedTxParts { tx, txid_parts }))
}
}
}
/// Frees a precomputed transaction from `zcash_transaction_precomputed_init`.
#[no_mangle]
pub extern "C" fn zcash_transaction_precomputed_free(precomputed_tx: *mut PrecomputedTxParts) {
if !precomputed_tx.is_null() {
drop(unsafe { Box::from_raw(precomputed_tx) });
}
}
/// Calculates a signature digest for the given transaction.
///
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
/// will be unaltered.
#[no_mangle]
pub extern "C" fn zcash_transaction_transparent_signature_digest(
precomputed_tx: *const PrecomputedTxParts,
hash_type: u32,
index: size_t,
script_code: *const c_uchar,
script_code_len: size_t,
value: i64,
sighash_ret: *mut [u8; 32],
) -> bool {
let precomputed_tx = if let Some(res) = unsafe { precomputed_tx.as_ref() } {
res
} else {
error!("Invalid precomputed transaction");
return false;
};
let script_code =
match Script::read(unsafe { slice::from_raw_parts(script_code, script_code_len) }) {
Ok(res) => res,
Err(e) => {
error!("Invalid scriptCode: {}", e);
return false;
}
};
let value = match Amount::from_i64(value) {
Ok(res) => res,
Err(()) => {
error!("Invalid amount");
return false;
}
};
let signable_input = SignableInput::transparent(index, &script_code, value);
let sighash = match precomputed_tx.tx.version() {
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling => {
// We don't support these legacy transaction formats in this API.
return false;
}
_ => signature_hash(
&precomputed_tx.tx,
hash_type,
&signable_input,
&precomputed_tx.txid_parts,
),
};
*unsafe { &mut *sighash_ret } = *sighash.as_ref();
true
}

View File

@ -15,6 +15,7 @@
#include "uint256.h"
#include <librustzcash.h>
#include <rust/transaction.h>
using namespace std;
@ -1127,20 +1128,41 @@ uint256 GetShieldedOutputsHash(const CTransaction& txTo) {
} // anon namespace
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo)
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo) :
preTx(nullptr, zcash_transaction_precomputed_free)
{
hashPrevouts = GetPrevoutHash(txTo);
hashSequence = GetSequenceHash(txTo);
hashOutputs = GetOutputsHash(txTo);
hashJoinSplits = GetJoinSplitsHash(txTo);
hashShieldedSpends = GetShieldedSpendsHash(txTo);
hashShieldedOutputs = GetShieldedOutputsHash(txTo);
bool isOverwinterV3 =
txTo.fOverwintered &&
txTo.nVersionGroupId == OVERWINTER_VERSION_GROUP_ID &&
txTo.nVersion == OVERWINTER_TX_VERSION;
bool isSaplingV4 =
txTo.fOverwintered &&
txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID &&
txTo.nVersion == SAPLING_TX_VERSION;
if (!txTo.fOverwintered || isOverwinterV3 || isSaplingV4) {
hashPrevouts = GetPrevoutHash(txTo);
hashSequence = GetSequenceHash(txTo);
hashOutputs = GetOutputsHash(txTo);
hashJoinSplits = GetJoinSplitsHash(txTo);
hashShieldedSpends = GetShieldedSpendsHash(txTo);
hashShieldedOutputs = GetShieldedOutputsHash(txTo);
} else {
// TODO: If we already have this serialized, use it.
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << txTo;
preTx.reset(zcash_transaction_precomputed_init(
reinterpret_cast<const unsigned char*>(ss.data()),
ss.size()));
}
}
SigVersion SignatureHashVersion(const CTransaction& txTo)
{
if (txTo.fOverwintered) {
if (txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID) {
if (txTo.nVersionGroupId == ZIP225_VERSION_GROUP_ID) {
return SIGVERSION_ZIP244;
} else if (txTo.nVersionGroupId == SAPLING_VERSION_GROUP_ID) {
return SIGVERSION_SAPLING;
} else {
return SIGVERSION_OVERWINTER;
@ -1166,7 +1188,42 @@ uint256 SignatureHash(
auto sigversion = SignatureHashVersion(txTo);
if (sigversion == SIGVERSION_OVERWINTER || sigversion == SIGVERSION_SAPLING) {
if (sigversion == SIGVERSION_ZIP244) {
// The consensusBranchId parameter is ignored; we use the value stored
// in the transaction itself.
if (nIn == NOT_AN_INPUT) {
// The signature digest is just the txid! No need to cross the FFI.
return txTo.GetHash();
} else {
CDataStream sScriptCode(SER_NETWORK, PROTOCOL_VERSION);
sScriptCode << *(CScriptBase*)(&scriptCode);
if (cache) {
uint256 hash;
zcash_transaction_transparent_signature_digest(
cache->preTx.get(),
nHashType,
nIn,
reinterpret_cast<const unsigned char*>(sScriptCode.data()),
sScriptCode.size(),
amount,
hash.begin());
return hash;
} else {
PrecomputedTransactionData local(txTo);
uint256 hash;
zcash_transaction_transparent_signature_digest(
local.preTx.get(),
nHashType,
nIn,
reinterpret_cast<const unsigned char*>(sScriptCode.data()),
sScriptCode.size(),
amount,
hash.begin());
return hash;
}
}
} else if (sigversion == SIGVERSION_OVERWINTER || sigversion == SIGVERSION_SAPLING) {
uint256 hashPrevouts;
uint256 hashSequence;
uint256 hashOutputs;

View File

@ -9,6 +9,8 @@
#include "script_error.h"
#include "primitives/transaction.h"
#include <rust/transaction.h>
#include <vector>
#include <stdint.h>
#include <string>
@ -93,6 +95,8 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i
struct PrecomputedTransactionData
{
uint256 hashPrevouts, hashSequence, hashOutputs, hashJoinSplits, hashShieldedSpends, hashShieldedOutputs;
/** Precomputed transaction parts. */
std::unique_ptr<PrecomputedTxParts, decltype(&zcash_transaction_precomputed_free)> preTx;
PrecomputedTransactionData(const CTransaction& tx);
};
@ -102,6 +106,7 @@ enum SigVersion
SIGVERSION_SPROUT = 0,
SIGVERSION_OVERWINTER = 1,
SIGVERSION_SAPLING = 2,
SIGVERSION_ZIP244 = 3,
};
uint256 SignatureHash(

View File

@ -872,6 +872,81 @@ BOOST_AUTO_TEST_CASE(TxV5)
// ZIP 244: Check the transaction digests.
BOOST_CHECK_EQUAL(tx.GetHash().GetHex(), test[1].getValStr());
BOOST_CHECK_EQUAL(tx.GetAuthDigest().GetHex(), test[2].getValStr());
// ZIP 244: Check the signature digests.
unsigned int nIn = NOT_AN_INPUT;
if (!test[3].isNull()) {
nIn = test[3].get_int();
}
CScript scriptCode;
if (!test[4].isNull()) {
auto scriptCodeBytes = ParseHex(test[4].get_str());
scriptCode = CScript(scriptCodeBytes.begin(), scriptCodeBytes.end());
}
CAmount amount;
if (!test[5].isNull()) {
amount = test[5].get_int64();
}
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_ALL,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[6].getValStr());
if (!test[7].isNull()) {
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_NONE,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[7].getValStr());
}
if (!test[8].isNull()) {
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_SINGLE,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[8].getValStr());
}
if (!test[9].isNull()) {
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_ALL | SIGHASH_ANYONECANPAY,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[9].getValStr());
}
if (!test[10].isNull()) {
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_NONE | SIGHASH_ANYONECANPAY,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[10].getValStr());
}
if (!test[11].isNull()) {
BOOST_CHECK_EQUAL(
SignatureHash(
scriptCode, tx, nIn,
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
amount, tx.GetConsensusBranchId()
).GetHex(),
test[11].getValStr());
}
}
}