Add OrchardWallet::GetTxActions
This method detects and returns metadata for any notes belonging to the wallet that are spent in the transaction. It also trial-decrypts the Orchard outputs of a transaction using the wallet's incoming viewing keys along a set of OVKs provided by the caller, and returns metadata about the notes that were successfully decrypted.
This commit is contained in:
parent
9993d948ef
commit
315392ce3b
|
@ -11,7 +11,7 @@ replace-with = "vendored-sources"
|
|||
|
||||
[source."https://github.com/zcash/orchard.git"]
|
||||
git = "https://github.com/zcash/orchard.git"
|
||||
rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc"
|
||||
rev = "f4587f790d7317df85a9ee77ce693a06ed6d8d02"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."https://github.com/zcash/incrementalmerkletree.git"]
|
||||
|
|
|
@ -1242,7 +1242,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
[[package]]
|
||||
name = "orchard"
|
||||
version = "0.1.0-beta.1"
|
||||
source = "git+https://github.com/zcash/orchard.git?rev=a5f701f3186fb619b40f2dee9939e64e4c9eb7cc#a5f701f3186fb619b40f2dee9939e64e4c9eb7cc"
|
||||
source = "git+https://github.com/zcash/orchard.git?rev=f4587f790d7317df85a9ee77ce693a06ed6d8d02#f4587f790d7317df85a9ee77ce693a06ed6d8d02"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayvec 0.7.2",
|
||||
|
|
|
@ -88,7 +88,7 @@ codegen-units = 1
|
|||
[patch.crates-io]
|
||||
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
||||
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "62c33e4480a71170b02b9eb7d4b0160194f414ee" }
|
||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "a5f701f3186fb619b40f2dee9939e64e4c9eb7cc" }
|
||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "f4587f790d7317df85a9ee77ce693a06ed6d8d02" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }
|
||||
zcash_encoding = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "67cb63a5a6b1f2c4cf5a061dbe80e9a71477e289" }
|
||||
|
|
|
@ -263,6 +263,56 @@ void orchard_wallet_get_filtered_notes(
|
|||
push_note_callback_t push_cb
|
||||
);
|
||||
|
||||
/**
|
||||
* A C struct used to transfer Orchard action spend information across the FFI boundary.
|
||||
* This must have the same in-memory representation as the `FFIActionSpend` type in
|
||||
* orchard_ffi/wallet.rs.
|
||||
*/
|
||||
struct RawOrchardActionSpend {
|
||||
uint32_t spendActionIdx;
|
||||
unsigned char outpointTxId[32];
|
||||
uint32_t outpointActionIdx;
|
||||
CAmount noteValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* A C struct used to transfer Orchard action output information across the FFI boundary.
|
||||
* This must have the same in-memory representation as the `FFIActionOutput` type in
|
||||
* orchard_ffi/wallet.rs.
|
||||
*/
|
||||
struct RawOrchardActionOutput {
|
||||
uint32_t outputActionIdx;
|
||||
OrchardRawAddressPtr* addr;
|
||||
CAmount noteValue;
|
||||
unsigned char memo[512];
|
||||
bool isOutgoing;
|
||||
};
|
||||
|
||||
typedef void (*push_spend_t)(void* callbackReceiver, const RawOrchardActionSpend data);
|
||||
|
||||
typedef void (*push_output_t)(void* callbackReceiver, const RawOrchardActionOutput data);
|
||||
|
||||
/**
|
||||
* Trial-decrypt the specfied Orchard bundle, and
|
||||
* uses the provided callback to push RawOrchardActionData values corresponding to the
|
||||
* actions of that bundle on to the provided result vector. Note that the push_cb callback can perform any
|
||||
* necessary conversion from a RawOrchardActionData value in addition to modifying the
|
||||
* provided result vector.
|
||||
*
|
||||
* The following pointers must be freed by the caller:
|
||||
* - `RawOrchardActionData::spend` must be freed using `orchard_action_spend_free`
|
||||
* - `RawOrchardActionData::output` must be freed using `orchard_action_output_free`
|
||||
* - `RawOrchardActionData::output::addr` must be freed using `orchard_address_free`
|
||||
*/
|
||||
bool orchard_wallet_get_txdata(
|
||||
const OrchardWalletPtr* wallet,
|
||||
const OrchardBundlePtr* bundle,
|
||||
const unsigned char*,
|
||||
size_t raw_ovks_len,
|
||||
void* callbackReceiver,
|
||||
push_spend_t push_spend_cb,
|
||||
push_output_t push_output_cb
|
||||
);
|
||||
|
||||
typedef void (*push_txid_callback_t)(void* resultVector, unsigned char txid[32]);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use zcash_primitives::{
|
|||
|
||||
use orchard::{
|
||||
bundle::Authorized,
|
||||
keys::{FullViewingKey, IncomingViewingKey, SpendingKey},
|
||||
keys::{FullViewingKey, IncomingViewingKey, OutgoingViewingKey, SpendingKey},
|
||||
note::Nullifier,
|
||||
tree::{MerkleHashOrchard, MerklePath},
|
||||
Address, Bundle, Note,
|
||||
|
@ -359,7 +359,7 @@ impl Wallet {
|
|||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_for_keys(&keys) {
|
||||
for (action_idx, ivk, note, recipient, memo) in bundle.decrypt_outputs_with_keys(&keys) {
|
||||
assert!(self.add_decrypted_note(
|
||||
None,
|
||||
txid,
|
||||
|
@ -991,6 +991,116 @@ pub extern "C" fn orchard_wallet_get_filtered_notes(
|
|||
}
|
||||
}
|
||||
|
||||
/// A type used to pass decrypted spend information across the FFI boundary.
|
||||
/// This must have the same representation as `struct RawOrchardSpendData`
|
||||
/// in `rust/include/rust/orchard/wallet.h`.
|
||||
#[repr(C)]
|
||||
pub struct FFIActionSpend {
|
||||
spend_action_idx: u32,
|
||||
outpoint_txid: [u8; 32],
|
||||
outpoint_action_idx: u32,
|
||||
value: i64,
|
||||
}
|
||||
|
||||
/// A type used to pass decrypted output information across the FFI boundary.
|
||||
/// This must have the same representation as `struct RawOrchardOutputData`
|
||||
/// in `rust/include/rust/orchard/wallet.h`.
|
||||
#[repr(C)]
|
||||
pub struct FFIActionOutput {
|
||||
action_idx: u32,
|
||||
recipient: *mut Address,
|
||||
value: i64,
|
||||
memo: [u8; 512],
|
||||
is_outgoing: bool,
|
||||
}
|
||||
|
||||
/// A C++-allocated function pointer that can send a FFIActionSpend value
|
||||
/// to a receiver.
|
||||
pub type SpendPushCB = unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, data: FFIActionSpend);
|
||||
|
||||
/// A C++-allocated function pointer that can send a FFIActionOutput value
|
||||
/// to a receiver.
|
||||
pub type OutputPushCB =
|
||||
unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, data: FFIActionOutput);
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_wallet_get_txdata(
|
||||
wallet: *const Wallet,
|
||||
bundle: *const Bundle<Authorized, Amount>,
|
||||
raw_ovks: *const [u8; 32],
|
||||
raw_ovks_len: usize,
|
||||
callback_receiver: Option<FFICallbackReceiver>,
|
||||
spend_push_cb: Option<SpendPushCB>,
|
||||
output_push_cb: Option<OutputPushCB>,
|
||||
) -> bool {
|
||||
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
|
||||
let raw_ovks = unsafe { slice::from_raw_parts(raw_ovks, raw_ovks_len) };
|
||||
let ovks: Vec<OutgoingViewingKey> = raw_ovks
|
||||
.iter()
|
||||
.map(|k| OutgoingViewingKey::from(*k))
|
||||
.collect();
|
||||
if let Some(bundle) = unsafe { bundle.as_ref() } {
|
||||
let keys = wallet
|
||||
.key_store
|
||||
.viewing_keys
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let incoming: BTreeMap<usize, (Note, Address, [u8; 512])> = bundle
|
||||
.decrypt_outputs_with_keys(&keys)
|
||||
.into_iter()
|
||||
.map(|(idx, _, note, addr, memo)| (idx, (note, addr, memo)))
|
||||
.collect();
|
||||
|
||||
let outgoing: BTreeMap<usize, (Note, Address, [u8; 512])> = bundle
|
||||
.recover_outputs_with_ovks(&ovks)
|
||||
.into_iter()
|
||||
.map(|(idx, _, note, addr, memo)| (idx, (note, addr, memo)))
|
||||
.collect();
|
||||
|
||||
for (idx, action) in bundle.actions().iter().enumerate() {
|
||||
let nf = action.nullifier();
|
||||
if let Some(spend) = wallet.nullifiers.get(nf).and_then(|outpoint| {
|
||||
wallet
|
||||
.wallet_received_notes
|
||||
.get(&outpoint.txid)
|
||||
.and_then(|txnotes| txnotes.decrypted_notes.get(&outpoint.action_idx))
|
||||
.map(|dnote| FFIActionSpend {
|
||||
spend_action_idx: idx as u32,
|
||||
outpoint_txid: *outpoint.txid.as_ref(),
|
||||
outpoint_action_idx: outpoint.action_idx as u32,
|
||||
value: dnote.note.value().inner() as i64,
|
||||
})
|
||||
}) {
|
||||
unsafe { (spend_push_cb.unwrap())(callback_receiver, spend) };
|
||||
}
|
||||
if let Some((note, addr, memo)) = incoming.get(&idx) {
|
||||
let output = FFIActionOutput {
|
||||
action_idx: idx as u32,
|
||||
recipient: Box::into_raw(Box::new(*addr)),
|
||||
value: note.value().inner() as i64,
|
||||
memo: *memo,
|
||||
is_outgoing: false,
|
||||
};
|
||||
unsafe { (output_push_cb.unwrap())(callback_receiver, output) };
|
||||
} else if let Some((note, addr, memo)) = outgoing.get(&idx) {
|
||||
let output = FFIActionOutput {
|
||||
action_idx: idx as u32,
|
||||
recipient: Box::into_raw(Box::new(*addr)),
|
||||
value: note.value().inner() as i64,
|
||||
memo: *memo,
|
||||
is_outgoing: true,
|
||||
};
|
||||
unsafe { (output_push_cb.unwrap())(callback_receiver, output) };
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub type PushTxId = unsafe extern "C" fn(obj: Option<FFICallbackReceiver>, txid: *const [u8; 32]);
|
||||
|
||||
#[no_mangle]
|
||||
|
|
|
@ -110,6 +110,73 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class OrchardActionSpend {
|
||||
private:
|
||||
OrchardOutPoint outPoint;
|
||||
CAmount noteValue;
|
||||
public:
|
||||
OrchardActionSpend(OrchardOutPoint outPoint, CAmount noteValue): outPoint(outPoint), noteValue(noteValue) { }
|
||||
|
||||
OrchardOutPoint GetOutPoint() const {
|
||||
return outPoint;
|
||||
}
|
||||
|
||||
CAmount GetNoteValue() const {
|
||||
return noteValue;
|
||||
}
|
||||
};
|
||||
|
||||
class OrchardActionOutput {
|
||||
private:
|
||||
libzcash::OrchardRawAddress recipient;
|
||||
CAmount noteValue;
|
||||
std::array<unsigned char, 512> memo;
|
||||
bool isOutgoing;
|
||||
public:
|
||||
OrchardActionOutput(
|
||||
libzcash::OrchardRawAddress recipient, CAmount noteValue, std::array<unsigned char, 512> memo, bool isOutgoing):
|
||||
recipient(recipient), noteValue(noteValue), memo(memo), isOutgoing(isOutgoing) { }
|
||||
|
||||
const libzcash::OrchardRawAddress& GetRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
CAmount GetNoteValue() const {
|
||||
return noteValue;
|
||||
}
|
||||
|
||||
const std::array<unsigned char, 512>& GetMemo() const {
|
||||
return memo;
|
||||
}
|
||||
|
||||
bool IsOutgoing() const {
|
||||
return isOutgoing;
|
||||
}
|
||||
};
|
||||
|
||||
class OrchardActions {
|
||||
private:
|
||||
std::map<uint32_t, OrchardActionSpend> spends;
|
||||
std::map<uint32_t, OrchardActionOutput> outputs;
|
||||
public:
|
||||
OrchardActions() {}
|
||||
|
||||
void AddSpend(uint32_t actionIdx, OrchardActionSpend spend) {
|
||||
spends.insert({actionIdx, spend});
|
||||
}
|
||||
|
||||
void AddOutput(uint32_t actionIdx, OrchardActionOutput output) {
|
||||
outputs.insert({actionIdx, output});
|
||||
}
|
||||
|
||||
const std::map<uint32_t, OrchardActionSpend>& GetSpends() {
|
||||
return spends;
|
||||
}
|
||||
|
||||
const std::map<uint32_t, OrchardActionOutput>& GetOutputs() {
|
||||
return outputs;
|
||||
}
|
||||
};
|
||||
|
||||
class OrchardWallet
|
||||
{
|
||||
|
@ -355,6 +422,40 @@ public:
|
|||
void GarbageCollect() {
|
||||
orchard_wallet_gc_note_commitment_tree(inner.get());
|
||||
}
|
||||
|
||||
static void PushSpendAction(void* receiver, RawOrchardActionSpend rawSpend) {
|
||||
uint256 txid;
|
||||
std::move(std::begin(rawSpend.outpointTxId), std::end(rawSpend.outpointTxId), txid.begin());
|
||||
auto spend = OrchardActionSpend(
|
||||
OrchardOutPoint(txid, rawSpend.outpointActionIdx),
|
||||
rawSpend.noteValue);
|
||||
reinterpret_cast<OrchardActions*>(receiver)->AddSpend(rawSpend.spendActionIdx, spend);
|
||||
}
|
||||
|
||||
static void PushOutputAction(void* receiver, RawOrchardActionOutput rawOutput) {
|
||||
std::array<unsigned char, 512> memo;
|
||||
std::move(std::begin(rawOutput.memo), std::end(rawOutput.memo), memo.begin());
|
||||
auto output = OrchardActionOutput(
|
||||
libzcash::OrchardRawAddress(rawOutput.addr),
|
||||
rawOutput.noteValue,
|
||||
memo,
|
||||
rawOutput.isOutgoing);
|
||||
|
||||
reinterpret_cast<OrchardActions*>(receiver)->AddOutput(rawOutput.outputActionIdx, output);
|
||||
}
|
||||
|
||||
OrchardActions GetTxActions(const CTransaction& tx, const std::vector<uint256>& ovks) const {
|
||||
OrchardActions result;
|
||||
orchard_wallet_get_txdata(
|
||||
inner.get(),
|
||||
tx.GetOrchardBundle().inner.get(),
|
||||
reinterpret_cast<const unsigned char*>(ovks.data()),
|
||||
ovks.size(),
|
||||
&result,
|
||||
PushSpendAction,
|
||||
PushOutputAction);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class OrchardWalletNoteCommitmentTreeWriter
|
||||
|
|
|
@ -4168,6 +4168,30 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
|||
auto legacyAcctOVKs = legacyKey.GetOVKsForShielding();
|
||||
ovks.insert(legacyAcctOVKs.first);
|
||||
ovks.insert(legacyAcctOVKs.second);
|
||||
|
||||
// Generate the OVKs for shielding for all unified key components
|
||||
for (const auto& [_, ufvkid] : pwalletMain->mapUnifiedAccountKeys) {
|
||||
auto ufvk = pwalletMain->GetUnifiedFullViewingKey(ufvkid);
|
||||
if (ufvk.has_value()) {
|
||||
auto tkey = ufvk.value().GetTransparentKey();
|
||||
if (tkey.has_value()) {
|
||||
auto tovks = tkey.value().GetOVKsForShielding();
|
||||
ovks.insert(tovks.first);
|
||||
ovks.insert(tovks.second);
|
||||
}
|
||||
auto skey = ufvk.value().GetSaplingKey();
|
||||
if (skey.has_value()) {
|
||||
auto sovks = skey.value().GetOVKs();
|
||||
ovks.insert(sovks.first);
|
||||
ovks.insert(sovks.second);
|
||||
}
|
||||
auto okey = ufvk.value().GetOrchardKey();
|
||||
if (okey.has_value()) {
|
||||
ovks.insert(okey.value().ToExternalOutgoingViewingKey());
|
||||
ovks.insert(okey.value().ToInternalOutgoingViewingKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sapling spends
|
||||
|
|
Loading…
Reference in New Issue