ZIP 244 signature digests
This commit is contained in:
parent
bd1fd2eaca
commit
0b8a348c2b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue