Merge pull request #5658 from str4d/5570-tx-builder-orchard-spends
Add Orchard sender support to the transaction builder
This commit is contained in:
commit
0f59c5b926
|
@ -334,6 +334,7 @@ libbitcoin_wallet_a_SOURCES = \
|
||||||
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
||||||
wallet/crypter.cpp \
|
wallet/crypter.cpp \
|
||||||
wallet/db.cpp \
|
wallet/db.cpp \
|
||||||
|
wallet/orchard.cpp \
|
||||||
wallet/paymentdisclosure.cpp \
|
wallet/paymentdisclosure.cpp \
|
||||||
wallet/paymentdisclosuredb.cpp \
|
wallet/paymentdisclosuredb.cpp \
|
||||||
wallet/rpcdisclosure.cpp \
|
wallet/rpcdisclosure.cpp \
|
||||||
|
|
|
@ -233,7 +233,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orchardBundle.has_value()) {
|
if (orchardBundle.has_value()) {
|
||||||
auto authorizedBundle = orchardBundle.value().ProveAndSign(dataToBeSigned);
|
auto authorizedBundle = orchardBundle.value().ProveAndSign({}, dataToBeSigned);
|
||||||
if (authorizedBundle.has_value()) {
|
if (authorizedBundle.has_value()) {
|
||||||
mtx.orchardBundle = authorizedBundle.value();
|
mtx.orchardBundle = authorizedBundle.value();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// A type-safe pointer to a Rust-allocated struct containing the information
|
||||||
|
/// needed to spend an Orchard note.
|
||||||
|
struct OrchardSpendInfoPtr;
|
||||||
|
typedef struct OrchardSpendInfoPtr OrchardSpendInfoPtr;
|
||||||
|
|
||||||
/// Pointer to Rust-allocated Orchard bundle builder.
|
/// Pointer to Rust-allocated Orchard bundle builder.
|
||||||
struct OrchardBuilderPtr;
|
struct OrchardBuilderPtr;
|
||||||
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
||||||
|
@ -22,6 +27,10 @@ typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
||||||
struct OrchardUnauthorizedBundlePtr;
|
struct OrchardUnauthorizedBundlePtr;
|
||||||
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
|
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
|
||||||
|
|
||||||
|
/// Frees the memory associated with an Orchard spend info struct that was
|
||||||
|
/// allocated by Rust.
|
||||||
|
void orchard_spend_info_free(OrchardSpendInfoPtr* ptr);
|
||||||
|
|
||||||
/// Construct a new Orchard transaction builder.
|
/// Construct a new Orchard transaction builder.
|
||||||
///
|
///
|
||||||
/// If `anchor` is `null`, the root of the empty Orchard commitment tree is used.
|
/// If `anchor` is `null`, the root of the empty Orchard commitment tree is used.
|
||||||
|
@ -33,6 +42,16 @@ OrchardBuilderPtr* orchard_builder_new(
|
||||||
/// Frees an Orchard builder returned from `orchard_builder_new`.
|
/// Frees an Orchard builder returned from `orchard_builder_new`.
|
||||||
void orchard_builder_free(OrchardBuilderPtr* ptr);
|
void orchard_builder_free(OrchardBuilderPtr* ptr);
|
||||||
|
|
||||||
|
/// Adds a note to be spent in this bundle.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the Merkle path in `spend_info` does not have the
|
||||||
|
/// required anchor.
|
||||||
|
///
|
||||||
|
/// `spend_info` is always freed by this method, whether or not it succeeds.
|
||||||
|
bool orchard_builder_add_spend(
|
||||||
|
OrchardBuilderPtr* ptr,
|
||||||
|
OrchardSpendInfoPtr* spend_info);
|
||||||
|
|
||||||
/// Adds an address which will receive funds in this bundle.
|
/// Adds an address which will receive funds in this bundle.
|
||||||
///
|
///
|
||||||
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
|
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
|
||||||
|
@ -63,6 +82,8 @@ void orchard_unauthorized_bundle_free(OrchardUnauthorizedBundlePtr* bundle);
|
||||||
/// `bundle` is always freed by this method.
|
/// `bundle` is always freed by this method.
|
||||||
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
|
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
|
||||||
OrchardUnauthorizedBundlePtr* bundle,
|
OrchardUnauthorizedBundlePtr* bundle,
|
||||||
|
const OrchardSpendingKeyPtr** keys,
|
||||||
|
size_t keys_len,
|
||||||
const unsigned char* sighash);
|
const unsigned char* sighash);
|
||||||
|
|
||||||
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||||
|
|
|
@ -197,6 +197,24 @@ OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
|
||||||
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||||
const OrchardFullViewingKeyPtr* key);
|
const OrchardFullViewingKeyPtr* key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the external outgoing viewing key for the specified full viewing key.
|
||||||
|
*
|
||||||
|
* `ovk_ret` must be 32 bytes.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||||
|
const OrchardFullViewingKeyPtr* fvk,
|
||||||
|
uint8_t *ovk_ret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the internal outgoing viewing key for the specified full viewing key.
|
||||||
|
*
|
||||||
|
* `ovk_ret` must be 32 bytes.
|
||||||
|
*/
|
||||||
|
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||||
|
const OrchardFullViewingKeyPtr* fvk,
|
||||||
|
uint8_t *ovk_ret);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements equality testing between full viewing keys.
|
* Implements equality testing between full viewing keys.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
|
||||||
|
|
||||||
#include "rust/orchard/keys.h"
|
#include "rust/orchard/keys.h"
|
||||||
|
#include "rust/builder.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -188,6 +189,17 @@ bool orchard_wallet_add_raw_address(
|
||||||
const OrchardRawAddressPtr* addr,
|
const OrchardRawAddressPtr* addr,
|
||||||
const OrchardIncomingViewingKeyPtr* ivk);
|
const OrchardIncomingViewingKeyPtr* ivk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pointer to the Orchard spending key corresponding to the specified raw
|
||||||
|
* address, if it is known to the wallet, or `nullptr` otherwise.
|
||||||
|
*
|
||||||
|
* Memory is allocated by Rust and must be manually freed using
|
||||||
|
* `orchard_spending_key_free`.
|
||||||
|
*/
|
||||||
|
OrchardSpendingKeyPtr* orchard_wallet_get_spending_key_for_address(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const OrchardRawAddressPtr* addr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a pointer to the Orchard incoming viewing key corresponding to the specified
|
* Returns a pointer to the Orchard incoming viewing key corresponding to the specified
|
||||||
* raw address, if it is known to the wallet, or `nullptr` otherwise.
|
* raw address, if it is known to the wallet, or `nullptr` otherwise.
|
||||||
|
@ -251,6 +263,20 @@ void orchard_wallet_get_potential_spends(
|
||||||
push_txid_callback_t push_cb
|
push_txid_callback_t push_cb
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the information needed to spend the wallet note at the given outpoint,
|
||||||
|
* relative to the current root known to the wallet of the Orchard commitment
|
||||||
|
* tree.
|
||||||
|
*
|
||||||
|
* Returns `null` if the outpoint is not known to the wallet, or the Orchard
|
||||||
|
* bundle containing the note has not been passed to
|
||||||
|
* `orchard_wallet_append_bundle_commitments`.
|
||||||
|
*/
|
||||||
|
OrchardSpendInfoPtr* orchard_wallet_get_spend_info(
|
||||||
|
const OrchardWalletPtr* wallet,
|
||||||
|
const unsigned char txid[32],
|
||||||
|
uint32_t action_idx);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
use incrementalmerkletree::Hashable;
|
use incrementalmerkletree::Hashable;
|
||||||
|
use libc::size_t;
|
||||||
|
use orchard::keys::SpendingKey;
|
||||||
use orchard::{
|
use orchard::{
|
||||||
builder::{Builder, InProgress, Unauthorized, Unproven},
|
builder::{Builder, InProgress, Unauthorized, Unproven},
|
||||||
bundle::{Authorized, Flags},
|
bundle::{Authorized, Flags},
|
||||||
keys::OutgoingViewingKey,
|
keys::{FullViewingKey, OutgoingViewingKey},
|
||||||
tree::MerkleHashOrchard,
|
tree::{MerkleHashOrchard, MerklePath},
|
||||||
value::NoteValue,
|
value::NoteValue,
|
||||||
Bundle,
|
Bundle, Note,
|
||||||
};
|
};
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
@ -22,6 +25,29 @@ use zcash_primitives::transaction::{
|
||||||
|
|
||||||
use crate::{transaction_ffi::PrecomputedTxParts, ORCHARD_PK};
|
use crate::{transaction_ffi::PrecomputedTxParts, ORCHARD_PK};
|
||||||
|
|
||||||
|
pub struct OrchardSpendInfo {
|
||||||
|
fvk: FullViewingKey,
|
||||||
|
note: Note,
|
||||||
|
merkle_path: MerklePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrchardSpendInfo {
|
||||||
|
pub fn from_parts(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
|
||||||
|
OrchardSpendInfo {
|
||||||
|
fvk,
|
||||||
|
note,
|
||||||
|
merkle_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_spend_info_free(spend_info: *mut OrchardSpendInfo) {
|
||||||
|
if !spend_info.is_null() {
|
||||||
|
drop(unsafe { Box::from_raw(spend_info) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_builder_new(
|
pub extern "C" fn orchard_builder_new(
|
||||||
spends_enabled: bool,
|
spends_enabled: bool,
|
||||||
|
@ -37,6 +63,27 @@ pub extern "C" fn orchard_builder_new(
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_builder_add_spend(
|
||||||
|
builder: *mut Builder,
|
||||||
|
orchard_spend_info: *mut OrchardSpendInfo,
|
||||||
|
) -> bool {
|
||||||
|
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||||
|
let orchard_spend_info = unsafe { Box::from_raw(orchard_spend_info) };
|
||||||
|
|
||||||
|
match builder.add_spend(
|
||||||
|
orchard_spend_info.fvk,
|
||||||
|
orchard_spend_info.note,
|
||||||
|
orchard_spend_info.merkle_path,
|
||||||
|
) {
|
||||||
|
Ok(()) => true,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to add Orchard spend: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_builder_add_recipient(
|
pub extern "C" fn orchard_builder_add_recipient(
|
||||||
builder: *mut Builder,
|
builder: *mut Builder,
|
||||||
|
@ -100,15 +147,27 @@ pub extern "C" fn orchard_unauthorized_bundle_free(
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
|
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
|
||||||
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||||
|
keys: *const *const SpendingKey,
|
||||||
|
keys_len: size_t,
|
||||||
sighash: *const [u8; 32],
|
sighash: *const [u8; 32],
|
||||||
) -> *mut Bundle<Authorized, Amount> {
|
) -> *mut Bundle<Authorized, Amount> {
|
||||||
let bundle = unsafe { Box::from_raw(bundle) };
|
let bundle = unsafe { Box::from_raw(bundle) };
|
||||||
|
let keys = unsafe { slice::from_raw_parts(keys, keys_len) };
|
||||||
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
|
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
|
||||||
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
|
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
|
||||||
|
|
||||||
|
let signing_keys = keys
|
||||||
|
.iter()
|
||||||
|
.map(|sk| {
|
||||||
|
unsafe { sk.as_ref() }
|
||||||
|
.expect("SpendingKey pointers must not be null")
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let res = bundle
|
let res = bundle
|
||||||
.create_proof(pk)
|
.create_proof(pk)
|
||||||
.and_then(|b| b.apply_signatures(OsRng, *sighash, &[]));
|
.and_then(|b| b.apply_signatures(OsRng, *sighash, &signing_keys));
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(signed) => Box::into_raw(Box::new(signed)),
|
Ok(signed) => Box::into_raw(Box::new(signed)),
|
||||||
|
|
|
@ -2,8 +2,10 @@ use std::io::{Read, Write};
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use orchard::keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, SpendingKey};
|
use orchard::{
|
||||||
use orchard::Address;
|
keys::{DiversifierIndex, FullViewingKey, IncomingViewingKey, OutgoingViewingKey, SpendingKey},
|
||||||
|
Address,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
|
||||||
|
@ -282,6 +284,31 @@ pub extern "C" fn orchard_full_viewing_key_to_internal_incoming_viewing_key(
|
||||||
.unwrap_or(std::ptr::null_mut())
|
.unwrap_or(std::ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_full_viewing_key_to_external_outgoing_viewing_key(
|
||||||
|
fvk: *const FullViewingKey,
|
||||||
|
ovk_ret: *mut [u8; 32],
|
||||||
|
) {
|
||||||
|
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||||
|
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||||
|
|
||||||
|
let ovk = OutgoingViewingKey::from(fvk);
|
||||||
|
*ovk_ret = *ovk.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_full_viewing_key_to_internal_outgoing_viewing_key(
|
||||||
|
fvk: *const FullViewingKey,
|
||||||
|
ovk_ret: *mut [u8; 32],
|
||||||
|
) {
|
||||||
|
let fvk = unsafe { fvk.as_ref() }.expect("fvk must not be null");
|
||||||
|
let ovk_ret = unsafe { ovk_ret.as_mut() }.expect("ovk_ret must not be null");
|
||||||
|
|
||||||
|
let internal_fvk = fvk.derive_internal();
|
||||||
|
let ovk = OutgoingViewingKey::from(&internal_fvk);
|
||||||
|
*ovk_ret = *ovk.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_full_viewing_key_eq(
|
pub extern "C" fn orchard_full_viewing_key_eq(
|
||||||
k0: *const FullViewingKey,
|
k0: *const FullViewingKey,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree};
|
use std::convert::TryInto;
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Position, Tree};
|
||||||
use libc::c_uchar;
|
use libc::c_uchar;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::slice;
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
|
@ -14,11 +16,11 @@ use orchard::{
|
||||||
bundle::Authorized,
|
bundle::Authorized,
|
||||||
keys::{FullViewingKey, IncomingViewingKey, SpendingKey},
|
keys::{FullViewingKey, IncomingViewingKey, SpendingKey},
|
||||||
note::Nullifier,
|
note::Nullifier,
|
||||||
tree::MerkleHashOrchard,
|
tree::{MerkleHashOrchard, MerklePath},
|
||||||
Address, Bundle, Note,
|
Address, Bundle, Note,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::zcashd_orchard::OrderedAddress;
|
use crate::{builder_ffi::OrchardSpendInfo, zcashd_orchard::OrderedAddress};
|
||||||
|
|
||||||
use super::incremental_merkle_tree_ffi::MERKLE_DEPTH;
|
use super::incremental_merkle_tree_ffi::MERKLE_DEPTH;
|
||||||
|
|
||||||
|
@ -50,6 +52,8 @@ pub struct InPoint {
|
||||||
pub struct DecryptedNote {
|
pub struct DecryptedNote {
|
||||||
note: Note,
|
note: Note,
|
||||||
memo: [u8; 512],
|
memo: [u8; 512],
|
||||||
|
/// The position of the note's commitment within the global Merkle tree.
|
||||||
|
position: Option<Position>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A data structure tracking the note data that was decrypted from a single transaction,
|
/// A data structure tracking the note data that was decrypted from a single transaction,
|
||||||
|
@ -285,10 +289,13 @@ impl Wallet {
|
||||||
// observed the note is set to `None` as the transaction will no longer have been
|
// observed the note is set to `None` as the transaction will no longer have been
|
||||||
// observed as having been mined.
|
// observed as having been mined.
|
||||||
for (txid, n) in self.wallet_received_notes.iter_mut() {
|
for (txid, n) in self.wallet_received_notes.iter_mut() {
|
||||||
// Erase block height information for any received notes
|
// Erase block height and commitment tree information for any received
|
||||||
// that have been un-mined by the rewind.
|
// notes that have been un-mined by the rewind.
|
||||||
if !to_retain.contains(txid) {
|
if !to_retain.contains(txid) {
|
||||||
n.tx_height = None;
|
n.tx_height = None;
|
||||||
|
for dnote in n.decrypted_notes.values_mut() {
|
||||||
|
dnote.position = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.last_observed = Some(last_observed);
|
self.last_observed = Some(last_observed);
|
||||||
|
@ -411,7 +418,11 @@ impl Wallet {
|
||||||
self.nullifiers.insert(nf, outpoint);
|
self.nullifiers.insert(nf, outpoint);
|
||||||
|
|
||||||
// add the decrypted note data to the wallet
|
// add the decrypted note data to the wallet
|
||||||
let note_data = DecryptedNote { note, memo };
|
let note_data = DecryptedNote {
|
||||||
|
note,
|
||||||
|
memo,
|
||||||
|
position: None,
|
||||||
|
};
|
||||||
self.wallet_received_notes
|
self.wallet_received_notes
|
||||||
.entry(*txid)
|
.entry(*txid)
|
||||||
.or_insert_with(|| TxNotes {
|
.or_insert_with(|| TxNotes {
|
||||||
|
@ -476,7 +487,7 @@ impl Wallet {
|
||||||
tx_notes.tx_height = Some(block_height);
|
tx_notes.tx_height = Some(block_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let my_notes_for_tx = self.wallet_received_notes.get(txid);
|
let mut my_notes_for_tx = self.wallet_received_notes.get_mut(txid);
|
||||||
for (action_idx, action) in bundle.actions().iter().enumerate() {
|
for (action_idx, action) in bundle.actions().iter().enumerate() {
|
||||||
// append the note commitment for each action to the witness tree
|
// append the note commitment for each action to the witness tree
|
||||||
if !self
|
if !self
|
||||||
|
@ -487,8 +498,13 @@ impl Wallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// for notes that are ours, witness the current state of the tree
|
// for notes that are ours, witness the current state of the tree
|
||||||
if my_notes_for_tx.map_or(false, |n| n.decrypted_notes.contains_key(&action_idx)) {
|
if let Some(dnote) = my_notes_for_tx
|
||||||
self.witness_tree.witness();
|
.as_mut()
|
||||||
|
.and_then(|n| n.decrypted_notes.get_mut(&action_idx))
|
||||||
|
{
|
||||||
|
let (pos, cmx) = self.witness_tree.witness().expect("tree is not empty");
|
||||||
|
assert_eq!(cmx, MerkleHashOrchard::from_cmx(action.cmx()));
|
||||||
|
dnote.position = Some(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For nullifiers that are ours that we detect as spent by this action,
|
// For nullifiers that are ours that we detect as spent by this action,
|
||||||
|
@ -554,6 +570,42 @@ impl Wallet {
|
||||||
pub fn note_commitment_tree_root(&self) -> MerkleHashOrchard {
|
pub fn note_commitment_tree_root(&self) -> MerkleHashOrchard {
|
||||||
self.witness_tree.root()
|
self.witness_tree.root()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches the information necessary to spend the note at the given `OutPoint`,
|
||||||
|
/// relative to the current root known to the wallet of the Orchard commitment tree.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `OutPoint` is not known to the wallet, or the Orchard bundle
|
||||||
|
/// containing the note has not been passed to `Wallet::append_bundle_commitments`.
|
||||||
|
pub fn get_spend_info(&self, outpoint: OutPoint) -> Option<OrchardSpendInfo> {
|
||||||
|
// TODO: Take `confirmations` parameter and obtain the Merkle path to the root at
|
||||||
|
// that checkpoint, not the latest root.
|
||||||
|
self.wallet_received_notes
|
||||||
|
.get(&outpoint.txid)
|
||||||
|
.and_then(|tx_notes| tx_notes.decrypted_notes.get(&outpoint.action_idx))
|
||||||
|
.and_then(|dnote| {
|
||||||
|
self.key_store
|
||||||
|
.ivk_for_address(&dnote.note.recipient())
|
||||||
|
.and_then(|ivk| self.key_store.viewing_keys.get(ivk))
|
||||||
|
.zip(dnote.position)
|
||||||
|
.map(|(fvk, position)| {
|
||||||
|
let path = self
|
||||||
|
.witness_tree
|
||||||
|
.authentication_path(
|
||||||
|
position,
|
||||||
|
&MerkleHashOrchard::from_cmx(&dnote.note.commitment().into()),
|
||||||
|
)
|
||||||
|
.expect("wallet always has paths to positioned notes");
|
||||||
|
OrchardSpendInfo::from_parts(
|
||||||
|
fvk.clone(),
|
||||||
|
dnote.note,
|
||||||
|
MerklePath::from_parts(
|
||||||
|
u64::from(position).try_into().unwrap(),
|
||||||
|
path.try_into().unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -753,6 +805,22 @@ pub extern "C" fn orchard_wallet_add_raw_address(
|
||||||
wallet.key_store.add_raw_address(*addr, ivk.clone())
|
wallet.key_store.add_raw_address(*addr, ivk.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_wallet_get_spending_key_for_address(
|
||||||
|
wallet: *const Wallet,
|
||||||
|
addr: *const Address,
|
||||||
|
) -> *mut SpendingKey {
|
||||||
|
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
|
||||||
|
let addr = unsafe { addr.as_ref() }.expect("Address may not be null.");
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.key_store
|
||||||
|
.ivk_for_address(addr)
|
||||||
|
.and_then(|ivk| wallet.key_store.spending_key_for_ivk(ivk))
|
||||||
|
.map(|sk| Box::into_raw(Box::new(*sk)))
|
||||||
|
.unwrap_or(std::ptr::null_mut())
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn orchard_wallet_get_ivk_for_address(
|
pub extern "C" fn orchard_wallet_get_ivk_for_address(
|
||||||
wallet: *const Wallet,
|
wallet: *const Wallet,
|
||||||
|
@ -844,3 +912,26 @@ pub extern "C" fn orchard_wallet_get_potential_spends(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn orchard_wallet_get_spend_info(
|
||||||
|
wallet: *const Wallet,
|
||||||
|
txid: *const [c_uchar; 32],
|
||||||
|
action_idx: usize,
|
||||||
|
) -> *mut OrchardSpendInfo {
|
||||||
|
let wallet = unsafe { wallet.as_ref() }.expect("Wallet pointer may not be null.");
|
||||||
|
let txid = TxId::from_bytes(*unsafe { txid.as_ref() }.expect("txid may not be null."));
|
||||||
|
|
||||||
|
let outpoint = OutPoint { txid, action_idx };
|
||||||
|
|
||||||
|
if let Some(ret) = wallet.get_spend_info(outpoint) {
|
||||||
|
Box::into_raw(Box::new(ret))
|
||||||
|
} else {
|
||||||
|
tracing::error!(
|
||||||
|
"Requested note in action {} of transaction {} wasn't in the wallet",
|
||||||
|
outpoint.action_idx,
|
||||||
|
outpoint.txid
|
||||||
|
);
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -290,7 +290,7 @@ public:
|
||||||
uint256 orchardAnchor;
|
uint256 orchardAnchor;
|
||||||
uint256 dataToBeSigned;
|
uint256 dataToBeSigned;
|
||||||
auto builder = orchard::Builder(true, true, orchardAnchor);
|
auto builder = orchard::Builder(true, true, orchardAnchor);
|
||||||
mutableTx.orchardBundle = builder.Build().value().ProveAndSign(dataToBeSigned).value();
|
mutableTx.orchardBundle = builder.Build().value().ProveAndSign({}, dataToBeSigned).value();
|
||||||
orchardNullifier = mutableTx.orchardBundle.GetNullifiers()[0];
|
orchardNullifier = mutableTx.orchardBundle.GetNullifiers()[0];
|
||||||
|
|
||||||
tx = CTransaction(mutableTx);
|
tx = CTransaction(mutableTx);
|
||||||
|
@ -322,7 +322,7 @@ template<> void AppendRandomLeaf(OrchardMerkleFrontier &tree) {
|
||||||
uint256 orchardAnchor;
|
uint256 orchardAnchor;
|
||||||
uint256 dataToBeSigned;
|
uint256 dataToBeSigned;
|
||||||
auto builder = orchard::Builder(true, true, orchardAnchor);
|
auto builder = orchard::Builder(true, true, orchardAnchor);
|
||||||
auto bundle = builder.Build().value().ProveAndSign(dataToBeSigned).value();
|
auto bundle = builder.Build().value().ProveAndSign({}, dataToBeSigned).value();
|
||||||
tree.AppendBundle(bundle);
|
tree.AppendBundle(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,23 @@ Builder::Builder(
|
||||||
inner.reset(orchard_builder_new(spendsEnabled, outputsEnabled, anchor.IsNull() ? nullptr : anchor.begin()));
|
inner.reset(orchard_builder_new(spendsEnabled, outputsEnabled, anchor.IsNull() ? nullptr : anchor.begin()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Builder::AddSpend(orchard::SpendInfo spendInfo)
|
||||||
|
{
|
||||||
|
if (!inner) {
|
||||||
|
throw std::logic_error("orchard::Builder has already been used");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orchard_builder_add_spend(
|
||||||
|
inner.get(),
|
||||||
|
spendInfo.inner.release()))
|
||||||
|
{
|
||||||
|
hasActions = true;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Builder::AddOutput(
|
void Builder::AddOutput(
|
||||||
const std::optional<uint256>& ovk,
|
const std::optional<uint256>& ovk,
|
||||||
const libzcash::OrchardRawAddress& to,
|
const libzcash::OrchardRawAddress& to,
|
||||||
|
@ -75,14 +92,20 @@ std::optional<UnauthorizedBundle> Builder::Build() {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<OrchardBundle> UnauthorizedBundle::ProveAndSign(
|
std::optional<OrchardBundle> UnauthorizedBundle::ProveAndSign(
|
||||||
|
const std::vector<libzcash::OrchardSpendingKey>& keys,
|
||||||
uint256 sighash)
|
uint256 sighash)
|
||||||
{
|
{
|
||||||
if (!inner) {
|
if (!inner) {
|
||||||
throw std::logic_error("orchard::UnauthorizedBundle has already been used");
|
throw std::logic_error("orchard::UnauthorizedBundle has already been used");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<const OrchardSpendingKeyPtr*> pKeys;
|
||||||
|
for (const auto& key : keys) {
|
||||||
|
pKeys.push_back(key.inner.get());
|
||||||
|
}
|
||||||
|
|
||||||
auto authorizedBundle = orchard_unauthorized_bundle_prove_and_sign(
|
auto authorizedBundle = orchard_unauthorized_bundle_prove_and_sign(
|
||||||
inner.release(), sighash.begin());
|
inner.release(), pKeys.data(), pKeys.size(), sighash.begin());
|
||||||
if (authorizedBundle == nullptr) {
|
if (authorizedBundle == nullptr) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
} else {
|
} else {
|
||||||
|
@ -264,6 +287,34 @@ void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
|
||||||
mtx.nExpiryHeight = nExpiryHeight;
|
mtx.nExpiryHeight = nExpiryHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TransactionBuilder::AddOrchardSpend(
|
||||||
|
libzcash::OrchardSpendingKey sk,
|
||||||
|
orchard::SpendInfo spendInfo)
|
||||||
|
{
|
||||||
|
if (!orchardBuilder.has_value()) {
|
||||||
|
// Try to give a useful error.
|
||||||
|
if (!(jsInputs.empty() && jsOutputs.empty())) {
|
||||||
|
throw std::runtime_error("TransactionBuilder cannot spend Orchard notes in a Sprout transaction");
|
||||||
|
} else if (mtx.nVersion < ZIP225_MIN_TX_VERSION) {
|
||||||
|
throw std::runtime_error("TransactionBuilder cannot spend Orchard notes before NU5 activation");
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("TransactionBuilder cannot spend Orchard notes without Orchard anchor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fromAddr = spendInfo.FromAddress();
|
||||||
|
auto value = spendInfo.Value();
|
||||||
|
auto res = orchardBuilder.value().AddSpend(std::move(spendInfo));
|
||||||
|
if (res) {
|
||||||
|
orchardSpendingKeys.push_back(sk);
|
||||||
|
if (!firstOrchardSpendAddr.has_value()) {
|
||||||
|
firstOrchardSpendAddr = fromAddr;
|
||||||
|
}
|
||||||
|
valueBalanceOrchard += value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
void TransactionBuilder::AddOrchardOutput(
|
void TransactionBuilder::AddOrchardOutput(
|
||||||
const std::optional<uint256>& ovk,
|
const std::optional<uint256>& ovk,
|
||||||
const libzcash::OrchardRawAddress& to,
|
const libzcash::OrchardRawAddress& to,
|
||||||
|
@ -456,6 +507,9 @@ TransactionBuilderResult TransactionBuilder::Build()
|
||||||
} else if (tChangeAddr) {
|
} else if (tChangeAddr) {
|
||||||
// tChangeAddr has already been validated.
|
// tChangeAddr has already been validated.
|
||||||
AddTransparentOutput(tChangeAddr.value(), change);
|
AddTransparentOutput(tChangeAddr.value(), change);
|
||||||
|
} else if (firstOrchardSpendAddr.has_value()) {
|
||||||
|
auto ovk = orchardSpendingKeys[0].ToFullViewingKey().ToInternalOutgoingViewingKey();
|
||||||
|
AddOrchardOutput(ovk, firstOrchardSpendAddr.value(), change, std::nullopt);
|
||||||
} else if (!spends.empty()) {
|
} else if (!spends.empty()) {
|
||||||
auto fvk = spends[0].expsk.full_viewing_key();
|
auto fvk = spends[0].expsk.full_viewing_key();
|
||||||
auto note = spends[0].note;
|
auto note = spends[0].note;
|
||||||
|
@ -586,7 +640,8 @@ TransactionBuilderResult TransactionBuilder::Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orchardBundle.has_value()) {
|
if (orchardBundle.has_value()) {
|
||||||
auto authorizedBundle = orchardBundle.value().ProveAndSign(dataToBeSigned);
|
auto authorizedBundle = orchardBundle.value().ProveAndSign(
|
||||||
|
orchardSpendingKeys, dataToBeSigned);
|
||||||
if (authorizedBundle.has_value()) {
|
if (authorizedBundle.has_value()) {
|
||||||
mtx.orchardBundle = authorizedBundle.value();
|
mtx.orchardBundle = authorizedBundle.value();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#define NO_MEMO {{0xF6}}
|
#define NO_MEMO {{0xF6}}
|
||||||
|
|
||||||
|
class OrchardWallet;
|
||||||
namespace orchard { class UnauthorizedBundle; }
|
namespace orchard { class UnauthorizedBundle; }
|
||||||
|
|
||||||
uint256 ProduceZip244SignatureHash(
|
uint256 ProduceZip244SignatureHash(
|
||||||
|
@ -33,6 +34,45 @@ uint256 ProduceZip244SignatureHash(
|
||||||
|
|
||||||
namespace orchard {
|
namespace orchard {
|
||||||
|
|
||||||
|
/// The information necessary to spend an Orchard note.
|
||||||
|
class SpendInfo
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// Memory is allocated by Rust.
|
||||||
|
std::unique_ptr<OrchardSpendInfoPtr, decltype(&orchard_spend_info_free)> inner;
|
||||||
|
libzcash::OrchardRawAddress from;
|
||||||
|
uint64_t noteValue;
|
||||||
|
|
||||||
|
// SpendInfo() : inner(nullptr, orchard_spend_info_free) {}
|
||||||
|
SpendInfo(
|
||||||
|
OrchardSpendInfoPtr* spendInfo,
|
||||||
|
libzcash::OrchardRawAddress fromIn,
|
||||||
|
uint64_t noteValueIn
|
||||||
|
) : inner(spendInfo, orchard_spend_info_free), from(fromIn), noteValue(noteValueIn) {}
|
||||||
|
|
||||||
|
friend class Builder;
|
||||||
|
friend class ::OrchardWallet;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// SpendInfo should never be copied
|
||||||
|
SpendInfo(const SpendInfo&) = delete;
|
||||||
|
SpendInfo& operator=(const SpendInfo&) = delete;
|
||||||
|
SpendInfo(SpendInfo&& spendInfo) :
|
||||||
|
inner(std::move(spendInfo.inner)), from(std::move(spendInfo.from)), noteValue(std::move(spendInfo.noteValue)) {}
|
||||||
|
SpendInfo& operator=(SpendInfo&& spendInfo)
|
||||||
|
{
|
||||||
|
if (this != &spendInfo) {
|
||||||
|
inner = std::move(spendInfo.inner);
|
||||||
|
from = std::move(spendInfo.from);
|
||||||
|
noteValue = std::move(spendInfo.noteValue);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline libzcash::OrchardRawAddress FromAddress() const { return from; };
|
||||||
|
inline uint64_t Value() const { return noteValue; };
|
||||||
|
};
|
||||||
|
|
||||||
/// A builder that constructs an `UnauthorizedBundle` from a set of notes to be spent,
|
/// A builder that constructs an `UnauthorizedBundle` from a set of notes to be spent,
|
||||||
/// and recipients to receive funds.
|
/// and recipients to receive funds.
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -60,6 +100,12 @@ public:
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a note to be spent in this bundle.
|
||||||
|
///
|
||||||
|
/// Returns `false` if the given Merkle path does not have the required anchor
|
||||||
|
/// for the given note.
|
||||||
|
bool AddSpend(orchard::SpendInfo spendInfo);
|
||||||
|
|
||||||
/// Adds an address which will receive funds in this bundle.
|
/// Adds an address which will receive funds in this bundle.
|
||||||
void AddOutput(
|
void AddOutput(
|
||||||
const std::optional<uint256>& ovk,
|
const std::optional<uint256>& ovk,
|
||||||
|
@ -124,7 +170,8 @@ public:
|
||||||
/// this bundle must be discarded and a new bundle built. Subsequent usage of this
|
/// this bundle must be discarded and a new bundle built. Subsequent usage of this
|
||||||
/// object in any way will cause an exception. This emulates Rust's compile-time
|
/// object in any way will cause an exception. This emulates Rust's compile-time
|
||||||
/// move semantics at runtime.
|
/// move semantics at runtime.
|
||||||
std::optional<OrchardBundle> ProveAndSign(uint256 sighash);
|
std::optional<OrchardBundle> ProveAndSign(
|
||||||
|
const std::vector<libzcash::OrchardSpendingKey>& keys, uint256 sighash);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace orchard
|
} // namespace orchard
|
||||||
|
@ -223,6 +270,8 @@ private:
|
||||||
std::optional<orchard::Builder> orchardBuilder;
|
std::optional<orchard::Builder> orchardBuilder;
|
||||||
CAmount valueBalanceOrchard = 0;
|
CAmount valueBalanceOrchard = 0;
|
||||||
|
|
||||||
|
std::vector<libzcash::OrchardSpendingKey> orchardSpendingKeys;
|
||||||
|
std::optional<libzcash::OrchardRawAddress> firstOrchardSpendAddr;
|
||||||
std::vector<SpendDescriptionInfo> spends;
|
std::vector<SpendDescriptionInfo> spends;
|
||||||
std::vector<OutputDescriptionInfo> outputs;
|
std::vector<OutputDescriptionInfo> outputs;
|
||||||
std::vector<libzcash::JSInput> jsInputs;
|
std::vector<libzcash::JSInput> jsInputs;
|
||||||
|
@ -293,6 +342,10 @@ public:
|
||||||
|
|
||||||
void SetFee(CAmount fee);
|
void SetFee(CAmount fee);
|
||||||
|
|
||||||
|
bool AddOrchardSpend(
|
||||||
|
libzcash::OrchardSpendingKey sk,
|
||||||
|
orchard::SpendInfo spendInfo);
|
||||||
|
|
||||||
void AddOrchardOutput(
|
void AddOrchardOutput(
|
||||||
const std::optional<uint256>& ovk,
|
const std::optional<uint256>& ovk,
|
||||||
const libzcash::OrchardRawAddress& to,
|
const libzcash::OrchardRawAddress& to,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "consensus/validation.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
#include "utiltest.h"
|
#include "utiltest.h"
|
||||||
|
@ -75,3 +76,82 @@ TEST(OrchardWalletTests, TxContainsMyNotes) {
|
||||||
|
|
||||||
RegtestDeactivateNU5();
|
RegtestDeactivateNU5();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test is here instead of test_transaction_builder.cpp because it depends
|
||||||
|
// on OrchardWallet, which only exists if the wallet is compiled in.
|
||||||
|
TEST(TransactionBuilder, OrchardToOrchard) {
|
||||||
|
auto consensusParams = RegtestActivateNU5();
|
||||||
|
OrchardWallet wallet;
|
||||||
|
|
||||||
|
CBasicKeyStore keystore;
|
||||||
|
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
||||||
|
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||||
|
|
||||||
|
auto sk = RandomOrchardSpendingKey();
|
||||||
|
wallet.AddSpendingKey(sk);
|
||||||
|
|
||||||
|
// Create a transaction sending to the default address for that
|
||||||
|
// spending key and add it to the wallet.
|
||||||
|
libzcash::diversifier_index_t j(0);
|
||||||
|
auto txRecv = FakeOrchardTx(sk, j);
|
||||||
|
wallet.AddNotesIfInvolvingMe(txRecv);
|
||||||
|
|
||||||
|
// Generate a recipient.
|
||||||
|
auto recipient = RandomOrchardSpendingKey()
|
||||||
|
.ToFullViewingKey()
|
||||||
|
.ToIncomingViewingKey()
|
||||||
|
.Address(j);
|
||||||
|
|
||||||
|
// Select the one note in the wallet for spending.
|
||||||
|
std::vector<OrchardNoteMetadata> notes;
|
||||||
|
wallet.GetFilteredNotes(
|
||||||
|
notes, sk.ToFullViewingKey().ToIncomingViewingKey(), true, true);
|
||||||
|
ASSERT_EQ(notes.size(), 1);
|
||||||
|
|
||||||
|
// If we attempt to get spend info now, it will fail because the note hasn't
|
||||||
|
// been witnessed in the Orchard commitment tree.
|
||||||
|
EXPECT_THROW(wallet.GetSpendInfo(notes), std::logic_error);
|
||||||
|
|
||||||
|
// Append the bundle to the wallet's commitment tree.
|
||||||
|
CBlock fakeBlock;
|
||||||
|
fakeBlock.vtx.resize(2);
|
||||||
|
fakeBlock.vtx[1] = txRecv;
|
||||||
|
ASSERT_TRUE(wallet.AppendNoteCommitments(2, fakeBlock));
|
||||||
|
|
||||||
|
// Now we can get spend info for the note.
|
||||||
|
auto spendInfo = wallet.GetSpendInfo(notes);
|
||||||
|
EXPECT_EQ(spendInfo[0].second.Value(), 40000);
|
||||||
|
|
||||||
|
// Get the root of the commitment tree.
|
||||||
|
OrchardMerkleFrontier tree;
|
||||||
|
tree.AppendBundle(txRecv.GetOrchardBundle());
|
||||||
|
auto orchardAnchor = tree.root();
|
||||||
|
|
||||||
|
// Create an Orchard-only transaction
|
||||||
|
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
||||||
|
auto builder = TransactionBuilder(consensusParams, 2, orchardAnchor);
|
||||||
|
EXPECT_TRUE(builder.AddOrchardSpend(sk, std::move(spendInfo[0].second)));
|
||||||
|
builder.AddOrchardOutput(std::nullopt, recipient, 25000, std::nullopt);
|
||||||
|
auto maybeTx = builder.Build();
|
||||||
|
EXPECT_TRUE(maybeTx.IsTx());
|
||||||
|
if (maybeTx.IsError()) {
|
||||||
|
std::cerr << "Failed to build transaction: " << maybeTx.GetError() << std::endl;
|
||||||
|
GTEST_FAIL();
|
||||||
|
}
|
||||||
|
auto tx = maybeTx.GetTxOrThrow();
|
||||||
|
|
||||||
|
EXPECT_EQ(tx.vin.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vout.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vJoinSplit.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
|
||||||
|
EXPECT_EQ(tx.vShieldedOutput.size(), 0);
|
||||||
|
EXPECT_TRUE(tx.GetOrchardBundle().IsPresent());
|
||||||
|
EXPECT_EQ(tx.GetOrchardBundle().GetValueBalance(), 10000);
|
||||||
|
|
||||||
|
CValidationState state;
|
||||||
|
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 3, true));
|
||||||
|
EXPECT_EQ(state.GetRejectReason(), "");
|
||||||
|
|
||||||
|
// Revert to default
|
||||||
|
RegtestDeactivateNU5();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) 2022 The Zcash developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||||
|
|
||||||
|
#include "wallet/orchard.h"
|
||||||
|
|
||||||
|
std::optional<libzcash::OrchardSpendingKey> OrchardWallet::GetSpendingKeyForAddress(
|
||||||
|
const libzcash::OrchardRawAddress& addr) const
|
||||||
|
{
|
||||||
|
auto skPtr = orchard_wallet_get_spending_key_for_address(inner.get(), addr.inner.get());
|
||||||
|
if (skPtr == nullptr) return std::nullopt;
|
||||||
|
return libzcash::OrchardSpendingKey(skPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> OrchardWallet::GetSpendInfo(
|
||||||
|
const std::vector<OrchardNoteMetadata>& noteMetadata) const
|
||||||
|
{
|
||||||
|
std::vector<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> result;
|
||||||
|
for (const auto& note : noteMetadata) {
|
||||||
|
auto pSpendInfo = orchard_wallet_get_spend_info(
|
||||||
|
inner.get(),
|
||||||
|
note.GetOutPoint().hash.begin(),
|
||||||
|
note.GetOutPoint().n);
|
||||||
|
if (pSpendInfo == nullptr) {
|
||||||
|
throw std::logic_error("Called OrchardWallet::GetSpendInfo with unknown outpoint");
|
||||||
|
} else {
|
||||||
|
auto spendInfo = orchard::SpendInfo(
|
||||||
|
pSpendInfo,
|
||||||
|
note.GetAddress(),
|
||||||
|
note.GetNoteValue());
|
||||||
|
|
||||||
|
auto sk = GetSpendingKeyForAddress(note.GetAddress());
|
||||||
|
if (sk.has_value()) {
|
||||||
|
result.push_back(std::pair(std::move(sk.value()), std::move(spendInfo)));
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("Unknown spending key for given outpoint");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
|
#include "transaction_builder.h"
|
||||||
|
|
||||||
#include "rust/orchard/keys.h"
|
#include "rust/orchard/keys.h"
|
||||||
#include "rust/orchard/wallet.h"
|
#include "rust/orchard/wallet.h"
|
||||||
#include "zcash/address/orchard.hpp"
|
#include "zcash/address/orchard.hpp"
|
||||||
|
@ -32,6 +34,10 @@ public:
|
||||||
return op;
|
return op;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const libzcash::OrchardRawAddress& GetAddress() const {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
void SetConfirmations(int c) {
|
void SetConfirmations(int c) {
|
||||||
confirmations = c;
|
confirmations = c;
|
||||||
}
|
}
|
||||||
|
@ -50,6 +56,8 @@ class OrchardWallet
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<OrchardWalletPtr, decltype(&orchard_wallet_free)> inner;
|
std::unique_ptr<OrchardWalletPtr, decltype(&orchard_wallet_free)> inner;
|
||||||
|
|
||||||
|
friend class ::orchard::UnauthorizedBundle;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OrchardWallet() : inner(orchard_wallet_new(), orchard_wallet_free) {}
|
OrchardWallet() : inner(orchard_wallet_new(), orchard_wallet_free) {}
|
||||||
OrchardWallet(OrchardWallet&& wallet_data) : inner(std::move(wallet_data.inner)) {}
|
OrchardWallet(OrchardWallet&& wallet_data) : inner(std::move(wallet_data.inner)) {}
|
||||||
|
@ -196,6 +204,9 @@ public:
|
||||||
orchard_wallet_add_full_viewing_key(inner.get(), fvk.inner.get());
|
orchard_wallet_add_full_viewing_key(inner.get(), fvk.inner.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<libzcash::OrchardSpendingKey> GetSpendingKeyForAddress(
|
||||||
|
const libzcash::OrchardRawAddress& addr) const;
|
||||||
|
|
||||||
std::optional<libzcash::OrchardIncomingViewingKey> GetIncomingViewingKeyForAddress(
|
std::optional<libzcash::OrchardIncomingViewingKey> GetIncomingViewingKeyForAddress(
|
||||||
const libzcash::OrchardRawAddress& addr) const {
|
const libzcash::OrchardRawAddress& addr) const {
|
||||||
auto ivkPtr = orchard_wallet_get_ivk_for_address(inner.get(), addr.inner.get());
|
auto ivkPtr = orchard_wallet_get_ivk_for_address(inner.get(), addr.inner.get());
|
||||||
|
@ -262,6 +273,9 @@ public:
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> GetSpendInfo(
|
||||||
|
const std::vector<OrchardNoteMetadata>& noteMetadata) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ZCASH_ORCHARD_WALLET_H
|
#endif // ZCASH_ORCHARD_WALLET_H
|
||||||
|
|
|
@ -3237,6 +3237,13 @@ void CWallet::GetSaplingNoteWitnesses(const std::vector<SaplingOutPoint>& notes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> CWallet::GetOrchardSpendInfo(
|
||||||
|
const std::vector<OrchardNoteMetadata>& orchardNoteMetadata) const
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_wallet);
|
||||||
|
return orchardWallet.GetSpendInfo(orchardNoteMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
isminetype CWallet::IsMine(const CTxIn &txin) const
|
isminetype CWallet::IsMine(const CTxIn &txin) const
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "primitives/block.h"
|
#include "primitives/block.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "tinyformat.h"
|
#include "tinyformat.h"
|
||||||
|
#include "transaction_builder.h"
|
||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "utilstrencodings.h"
|
#include "utilstrencodings.h"
|
||||||
|
@ -1689,6 +1690,8 @@ public:
|
||||||
const std::vector<SaplingOutPoint>& notes,
|
const std::vector<SaplingOutPoint>& notes,
|
||||||
std::vector<std::optional<SaplingWitness>>& witnesses,
|
std::vector<std::optional<SaplingWitness>>& witnesses,
|
||||||
uint256 &final_anchor);
|
uint256 &final_anchor);
|
||||||
|
std::vector<std::pair<libzcash::OrchardSpendingKey, orchard::SpendInfo>> GetOrchardSpendInfo(
|
||||||
|
const std::vector<OrchardNoteMetadata>& orchardNoteMetadata) const;
|
||||||
|
|
||||||
isminetype IsMine(const CTxIn& txin) const;
|
isminetype IsMine(const CTxIn& txin) const;
|
||||||
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
|
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
|
||||||
|
|
|
@ -31,6 +31,18 @@ OrchardIncomingViewingKey OrchardFullViewingKey::ToInternalIncomingViewingKey()
|
||||||
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_internal_incoming_viewing_key(inner.get()));
|
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_internal_incoming_viewing_key(inner.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint256 OrchardFullViewingKey::ToExternalOutgoingViewingKey() const {
|
||||||
|
uint256 ovk;
|
||||||
|
orchard_full_viewing_key_to_external_outgoing_viewing_key(inner.get(), ovk.begin());
|
||||||
|
return ovk;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 OrchardFullViewingKey::ToInternalOutgoingViewingKey() const {
|
||||||
|
uint256 ovk;
|
||||||
|
orchard_full_viewing_key_to_internal_outgoing_viewing_key(inner.get(), ovk.begin());
|
||||||
|
return ovk;
|
||||||
|
}
|
||||||
|
|
||||||
OrchardSpendingKey OrchardSpendingKey::ForAccount(
|
OrchardSpendingKey OrchardSpendingKey::ForAccount(
|
||||||
const HDSeed& seed,
|
const HDSeed& seed,
|
||||||
uint32_t bip44CoinType,
|
uint32_t bip44CoinType,
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
class OrchardWallet;
|
class OrchardWallet;
|
||||||
namespace orchard { class Builder; }
|
namespace orchard {
|
||||||
|
class Builder;
|
||||||
|
class UnauthorizedBundle;
|
||||||
|
}
|
||||||
|
|
||||||
namespace libzcash {
|
namespace libzcash {
|
||||||
|
|
||||||
|
@ -199,6 +202,10 @@ public:
|
||||||
|
|
||||||
OrchardIncomingViewingKey ToInternalIncomingViewingKey() const;
|
OrchardIncomingViewingKey ToInternalIncomingViewingKey() const;
|
||||||
|
|
||||||
|
uint256 ToExternalOutgoingViewingKey() const;
|
||||||
|
|
||||||
|
uint256 ToInternalOutgoingViewingKey() const;
|
||||||
|
|
||||||
OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key)
|
OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key)
|
||||||
{
|
{
|
||||||
if (this != &key) {
|
if (this != &key) {
|
||||||
|
@ -256,6 +263,7 @@ private:
|
||||||
OrchardSpendingKey(OrchardSpendingKeyPtr* ptr) :
|
OrchardSpendingKey(OrchardSpendingKeyPtr* ptr) :
|
||||||
inner(ptr, orchard_spending_key_free) {}
|
inner(ptr, orchard_spending_key_free) {}
|
||||||
|
|
||||||
|
friend class orchard::UnauthorizedBundle;
|
||||||
friend class ::OrchardWallet;
|
friend class ::OrchardWallet;
|
||||||
public:
|
public:
|
||||||
OrchardSpendingKey(OrchardSpendingKey&& key) : inner(std::move(key.inner)) {}
|
OrchardSpendingKey(OrchardSpendingKey&& key) : inner(std::move(key.inner)) {}
|
||||||
|
|
Loading…
Reference in New Issue