mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-02-01 17:01:10 +03:00
f09c91f626
* add payment proof struct and deser * rustfmt * adding proof export functions * rustfmt * add payment proof validation function * rustfmt * add RPC version of retrieve_payment_proof + doctest * rustfmt * add verify_proof rpc function and documentation for new functions * rustfmt * add export and verify commands * rustfmt * test + test framework fixes * rustfmt * check whether addresses belong to this wallet, output such when checking * rustfmt * remove raw pubkey address and replace with ov3 address in user-facing contexts * merge from master and rustfmt * doctests
630 lines
18 KiB
Rust
630 lines
18 KiB
Rust
// Copyright 2019 The Grin Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Transaction building functions
|
|
|
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
use std::io::Cursor;
|
|
use uuid::Uuid;
|
|
|
|
use crate::grin_core::consensus::valid_header_version;
|
|
use crate::grin_core::core::HeaderVersion;
|
|
use crate::grin_keychain::{Identifier, Keychain};
|
|
use crate::grin_util::secp::key::SecretKey;
|
|
use crate::grin_util::secp::pedersen;
|
|
use crate::grin_util::Mutex;
|
|
use crate::internal::{selection, updater};
|
|
use crate::slate::Slate;
|
|
use crate::types::{Context, NodeClient, StoredProofInfo, TxLogEntryType, WalletBackend};
|
|
use crate::{address, Error, ErrorKind};
|
|
use ed25519_dalek::Keypair as DalekKeypair;
|
|
use ed25519_dalek::PublicKey as DalekPublicKey;
|
|
use ed25519_dalek::SecretKey as DalekSecretKey;
|
|
use ed25519_dalek::Signature as DalekSignature;
|
|
|
|
// static for incrementing test UUIDs
|
|
lazy_static! {
|
|
static ref SLATE_COUNTER: Mutex<u8> = { Mutex::new(0) };
|
|
}
|
|
|
|
/// Creates a new slate for a transaction, can be called by anyone involved in
|
|
/// the transaction (sender(s), receiver(s))
|
|
pub fn new_tx_slate<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
amount: u64,
|
|
num_participants: usize,
|
|
use_test_rng: bool,
|
|
ttl_blocks: Option<u64>,
|
|
) -> Result<Slate, Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
|
let mut slate = Slate::blank(num_participants);
|
|
if let Some(b) = ttl_blocks {
|
|
slate.ttl_cutoff_height = Some(current_height + b);
|
|
}
|
|
if use_test_rng {
|
|
{
|
|
let sc = SLATE_COUNTER.lock();
|
|
let bytes = [4, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, *sc];
|
|
slate.id = Uuid::from_slice(&bytes).unwrap();
|
|
}
|
|
*SLATE_COUNTER.lock() += 1;
|
|
}
|
|
slate.amount = amount;
|
|
slate.height = current_height;
|
|
|
|
if valid_header_version(current_height, HeaderVersion(1)) {
|
|
slate.version_info.block_header_version = 1;
|
|
}
|
|
|
|
if valid_header_version(current_height, HeaderVersion(2)) {
|
|
slate.version_info.block_header_version = 2;
|
|
}
|
|
|
|
if valid_header_version(current_height, HeaderVersion(3)) {
|
|
slate.version_info.block_header_version = 3;
|
|
}
|
|
|
|
// Set the lock_height explicitly to 0 here.
|
|
// This will generate a Plain kernel (rather than a HeightLocked kernel).
|
|
slate.lock_height = 0;
|
|
|
|
Ok(slate)
|
|
}
|
|
|
|
/// Estimates locked amount and fee for the transaction without creating one
|
|
pub fn estimate_send_tx<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
amount: u64,
|
|
minimum_confirmations: u64,
|
|
max_outputs: usize,
|
|
num_change_outputs: usize,
|
|
selection_strategy_is_use_all: bool,
|
|
parent_key_id: &Identifier,
|
|
) -> Result<
|
|
(
|
|
u64, // total
|
|
u64, // fee
|
|
),
|
|
Error,
|
|
>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
// Get lock height
|
|
let current_height = wallet.w2n_client().get_chain_tip()?.0;
|
|
// ensure outputs we're selecting are up to date
|
|
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
|
|
|
|
// Sender selects outputs into a new slate and save our corresponding keys in
|
|
// a transaction context. The secret key in our transaction context will be
|
|
// randomly selected. This returns the public slate, and a closure that locks
|
|
// our inputs and outputs once we're convinced the transaction exchange went
|
|
// according to plan
|
|
// This function is just a big helper to do all of that, in theory
|
|
// this process can be split up in any way
|
|
let (_coins, total, _amount, fee) = selection::select_coins_and_fee(
|
|
wallet,
|
|
amount,
|
|
current_height,
|
|
minimum_confirmations,
|
|
max_outputs,
|
|
num_change_outputs,
|
|
selection_strategy_is_use_all,
|
|
parent_key_id,
|
|
)?;
|
|
Ok((total, fee))
|
|
}
|
|
|
|
/// Add inputs to the slate (effectively becoming the sender)
|
|
pub fn add_inputs_to_slate<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
slate: &mut Slate,
|
|
minimum_confirmations: u64,
|
|
max_outputs: usize,
|
|
num_change_outputs: usize,
|
|
selection_strategy_is_use_all: bool,
|
|
parent_key_id: &Identifier,
|
|
participant_id: usize,
|
|
message: Option<String>,
|
|
is_initator: bool,
|
|
use_test_rng: bool,
|
|
) -> Result<Context, Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
// sender should always refresh outputs
|
|
updater::refresh_outputs(wallet, keychain_mask, parent_key_id, false)?;
|
|
|
|
// Sender selects outputs into a new slate and save our corresponding keys in
|
|
// a transaction context. The secret key in our transaction context will be
|
|
// randomly selected. This returns the public slate, and a closure that locks
|
|
// our inputs and outputs once we're convinced the transaction exchange went
|
|
// according to plan
|
|
// This function is just a big helper to do all of that, in theory
|
|
// this process can be split up in any way
|
|
let mut context = selection::build_send_tx(
|
|
wallet,
|
|
&wallet.keychain(keychain_mask)?,
|
|
keychain_mask,
|
|
slate,
|
|
minimum_confirmations,
|
|
max_outputs,
|
|
num_change_outputs,
|
|
selection_strategy_is_use_all,
|
|
parent_key_id.clone(),
|
|
use_test_rng,
|
|
)?;
|
|
|
|
// Generate a kernel offset and subtract from our context's secret key. Store
|
|
// the offset in the slate's transaction kernel, and adds our public key
|
|
// information to the slate
|
|
let _ = slate.fill_round_1(
|
|
&wallet.keychain(keychain_mask)?,
|
|
&mut context.sec_key,
|
|
&context.sec_nonce,
|
|
participant_id,
|
|
message,
|
|
use_test_rng,
|
|
)?;
|
|
|
|
if !is_initator {
|
|
// perform partial sig
|
|
let _ = slate.fill_round_2(
|
|
&wallet.keychain(keychain_mask)?,
|
|
&context.sec_key,
|
|
&context.sec_nonce,
|
|
participant_id,
|
|
)?;
|
|
}
|
|
|
|
Ok(context)
|
|
}
|
|
|
|
/// Add receiver output to the slate
|
|
pub fn add_output_to_slate<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
slate: &mut Slate,
|
|
parent_key_id: &Identifier,
|
|
participant_id: usize,
|
|
message: Option<String>,
|
|
is_initiator: bool,
|
|
use_test_rng: bool,
|
|
) -> Result<Context, Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
// create an output using the amount in the slate
|
|
let (_, mut context) = selection::build_recipient_output(
|
|
wallet,
|
|
keychain_mask,
|
|
slate,
|
|
parent_key_id.clone(),
|
|
use_test_rng,
|
|
)?;
|
|
|
|
// fill public keys
|
|
let _ = slate.fill_round_1(
|
|
&wallet.keychain(keychain_mask)?,
|
|
&mut context.sec_key,
|
|
&context.sec_nonce,
|
|
1,
|
|
message,
|
|
use_test_rng,
|
|
)?;
|
|
|
|
if !is_initiator {
|
|
// perform partial sig
|
|
let _ = slate.fill_round_2(
|
|
&wallet.keychain(keychain_mask)?,
|
|
&context.sec_key,
|
|
&context.sec_nonce,
|
|
participant_id,
|
|
)?;
|
|
}
|
|
|
|
Ok(context)
|
|
}
|
|
|
|
/// Complete a transaction
|
|
pub fn complete_tx<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
slate: &mut Slate,
|
|
participant_id: usize,
|
|
context: &Context,
|
|
) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
let _ = slate.fill_round_2(
|
|
&wallet.keychain(keychain_mask)?,
|
|
&context.sec_key,
|
|
&context.sec_nonce,
|
|
participant_id,
|
|
)?;
|
|
|
|
// Final transaction can be built by anyone at this stage
|
|
slate.finalize(&wallet.keychain(keychain_mask)?)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Rollback outputs associated with a transaction in the wallet
|
|
pub fn cancel_tx<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
parent_key_id: &Identifier,
|
|
tx_id: Option<u32>,
|
|
tx_slate_id: Option<Uuid>,
|
|
) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
let mut tx_id_string = String::new();
|
|
if let Some(tx_id) = tx_id {
|
|
tx_id_string = tx_id.to_string();
|
|
} else if let Some(tx_slate_id) = tx_slate_id {
|
|
tx_id_string = tx_slate_id.to_string();
|
|
}
|
|
let tx_vec = updater::retrieve_txs(wallet, tx_id, tx_slate_id, Some(&parent_key_id), false)?;
|
|
if tx_vec.len() != 1 {
|
|
return Err(ErrorKind::TransactionDoesntExist(tx_id_string))?;
|
|
}
|
|
let tx = tx_vec[0].clone();
|
|
if tx.tx_type != TxLogEntryType::TxSent && tx.tx_type != TxLogEntryType::TxReceived {
|
|
return Err(ErrorKind::TransactionNotCancellable(tx_id_string))?;
|
|
}
|
|
if tx.confirmed == true {
|
|
return Err(ErrorKind::TransactionNotCancellable(tx_id_string))?;
|
|
}
|
|
// get outputs associated with tx
|
|
let res = updater::retrieve_outputs(
|
|
wallet,
|
|
keychain_mask,
|
|
false,
|
|
Some(tx.id),
|
|
Some(&parent_key_id),
|
|
)?;
|
|
let outputs = res.iter().map(|m| m.output.clone()).collect();
|
|
updater::cancel_tx_and_outputs(wallet, keychain_mask, tx, outputs, parent_key_id)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Update the stored transaction (this update needs to happen when the TX is finalised)
|
|
pub fn update_stored_tx<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
context: &Context,
|
|
slate: &Slate,
|
|
is_invoiced: bool,
|
|
) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
// finalize command
|
|
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
|
|
let mut tx = None;
|
|
// don't want to assume this is the right tx, in case of self-sending
|
|
for t in tx_vec {
|
|
if t.tx_type == TxLogEntryType::TxSent && !is_invoiced {
|
|
tx = Some(t.clone());
|
|
break;
|
|
}
|
|
if t.tx_type == TxLogEntryType::TxReceived && is_invoiced {
|
|
tx = Some(t.clone());
|
|
break;
|
|
}
|
|
}
|
|
let mut tx = match tx {
|
|
Some(t) => t,
|
|
None => return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?,
|
|
};
|
|
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), &slate.tx)?;
|
|
let parent_key = tx.parent_key_id.clone();
|
|
tx.kernel_excess = Some(slate.tx.body.kernels[0].excess);
|
|
|
|
if let Some(ref p) = slate.payment_proof {
|
|
let derivation_index = match context.payment_proof_derivation_index {
|
|
Some(i) => i,
|
|
None => 0,
|
|
};
|
|
let keychain = wallet.keychain(keychain_mask)?;
|
|
let parent_key_id = wallet.parent_key_id();
|
|
let excess = slate.calc_excess(&keychain)?;
|
|
let sender_key =
|
|
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
|
let sender_address = address::ed25519_keypair(&sender_key)?.1;
|
|
let sig =
|
|
create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?;
|
|
tx.payment_proof = Some(StoredProofInfo {
|
|
receiver_address: p.receiver_address,
|
|
receiver_signature: p.receiver_signature,
|
|
sender_address_path: derivation_index,
|
|
sender_address,
|
|
sender_signature: Some(sig),
|
|
})
|
|
}
|
|
|
|
let mut batch = wallet.batch(keychain_mask)?;
|
|
batch.save_tx_log_entry(tx, &parent_key)?;
|
|
batch.commit()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Update the transaction participant messages
|
|
pub fn update_message<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
slate: &Slate,
|
|
) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), None, false)?;
|
|
if tx_vec.is_empty() {
|
|
return Err(ErrorKind::TransactionDoesntExist(slate.id.to_string()))?;
|
|
}
|
|
let mut batch = wallet.batch(keychain_mask)?;
|
|
for mut tx in tx_vec.into_iter() {
|
|
tx.messages = Some(slate.participant_messages());
|
|
let parent_key = tx.parent_key_id.clone();
|
|
batch.save_tx_log_entry(tx, &parent_key)?;
|
|
}
|
|
batch.commit()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn payment_proof_message(
|
|
amount: u64,
|
|
kernel_commitment: &pedersen::Commitment,
|
|
sender_address: DalekPublicKey,
|
|
) -> Result<Vec<u8>, Error> {
|
|
let mut msg = Vec::new();
|
|
msg.write_u64::<BigEndian>(amount)?;
|
|
msg.append(&mut kernel_commitment.0.to_vec());
|
|
msg.append(&mut sender_address.to_bytes().to_vec());
|
|
Ok(msg)
|
|
}
|
|
|
|
pub fn _decode_payment_proof_message(
|
|
msg: &Vec<u8>,
|
|
) -> Result<(u64, pedersen::Commitment, DalekPublicKey), Error> {
|
|
let mut rdr = Cursor::new(msg);
|
|
let amount = rdr.read_u64::<BigEndian>()?;
|
|
let mut commit_bytes = [0u8; 33];
|
|
for i in 0..33 {
|
|
commit_bytes[i] = rdr.read_u8()?;
|
|
}
|
|
let mut sender_address_bytes = [0u8; 32];
|
|
for i in 0..32 {
|
|
sender_address_bytes[i] = rdr.read_u8()?;
|
|
}
|
|
|
|
Ok((
|
|
amount,
|
|
pedersen::Commitment::from_vec(commit_bytes.to_vec()),
|
|
DalekPublicKey::from_bytes(&sender_address_bytes).unwrap(),
|
|
))
|
|
}
|
|
|
|
/// create a payment proof
|
|
pub fn create_payment_proof_signature(
|
|
amount: u64,
|
|
kernel_commitment: &pedersen::Commitment,
|
|
sender_address: DalekPublicKey,
|
|
sec_key: SecretKey,
|
|
) -> Result<DalekSignature, Error> {
|
|
let msg = payment_proof_message(amount, kernel_commitment, sender_address)?;
|
|
let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) {
|
|
Ok(k) => k,
|
|
Err(e) => {
|
|
return Err(ErrorKind::ED25519Key(format!("{}", e)).to_owned())?;
|
|
}
|
|
};
|
|
let pub_key: DalekPublicKey = (&d_skey).into();
|
|
let keypair = DalekKeypair {
|
|
public: pub_key,
|
|
secret: d_skey,
|
|
};
|
|
Ok(keypair.sign(&msg))
|
|
}
|
|
|
|
/// Verify all aspects of a completed payment proof on the current slate
|
|
pub fn verify_slate_payment_proof<'a, T: ?Sized, C, K>(
|
|
wallet: &mut T,
|
|
keychain_mask: Option<&SecretKey>,
|
|
parent_key_id: &Identifier,
|
|
context: &Context,
|
|
slate: &Slate,
|
|
) -> Result<(), Error>
|
|
where
|
|
T: WalletBackend<'a, C, K>,
|
|
C: NodeClient + 'a,
|
|
K: Keychain + 'a,
|
|
{
|
|
let tx_vec = updater::retrieve_txs(wallet, None, Some(slate.id), Some(parent_key_id), false)?;
|
|
if tx_vec.len() == 0 {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"TxLogEntry with original proof info not found (is account correct?)".to_owned(),
|
|
))?;
|
|
}
|
|
|
|
let orig_proof_info = tx_vec[0].clone().payment_proof;
|
|
|
|
if orig_proof_info.is_some() && slate.payment_proof.is_none() {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Expected Payment Proof for this Transaction is not present".to_owned(),
|
|
))?;
|
|
}
|
|
|
|
if let Some(ref p) = slate.payment_proof {
|
|
let orig_proof_info = match orig_proof_info {
|
|
Some(p) => p,
|
|
None => {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Original proof info not stored in tx".to_owned(),
|
|
))?;
|
|
}
|
|
};
|
|
let keychain = wallet.keychain(keychain_mask)?;
|
|
let index = match context.payment_proof_derivation_index {
|
|
Some(i) => i,
|
|
None => {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Payment proof derivation index required".to_owned(),
|
|
))?;
|
|
}
|
|
};
|
|
let orig_sender_sk =
|
|
address::address_from_derivation_path(&keychain, parent_key_id, index)?;
|
|
let orig_sender_address = address::ed25519_keypair(&orig_sender_sk)?.1;
|
|
if p.sender_address != orig_sender_address {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Sender address on slate does not match original sender address".to_owned(),
|
|
))?;
|
|
}
|
|
|
|
if orig_proof_info.receiver_address != p.receiver_address {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Recipient address on slate does not match original recipient address".to_owned(),
|
|
))?;
|
|
}
|
|
let msg = payment_proof_message(
|
|
slate.amount,
|
|
&slate.calc_excess(&keychain)?,
|
|
orig_sender_address,
|
|
)?;
|
|
let sig = match p.receiver_signature {
|
|
Some(s) => s,
|
|
None => {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Recipient did not provide requested proof signature".to_owned(),
|
|
))?;
|
|
}
|
|
};
|
|
|
|
if let Err(_) = p.receiver_address.verify(&msg, &sig) {
|
|
return Err(ErrorKind::PaymentProof(
|
|
"Invalid proof signature".to_owned(),
|
|
))?;
|
|
};
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use rand::rngs::mock::StepRng;
|
|
|
|
use crate::grin_core::core::KernelFeatures;
|
|
use crate::grin_core::libtx::{build, ProofBuilder};
|
|
use crate::grin_keychain::{
|
|
BlindSum, BlindingFactor, ExtKeychain, ExtKeychainPath, Keychain, SwitchCommitmentType,
|
|
};
|
|
use crate::grin_util::{secp, static_secp_instance};
|
|
|
|
#[test]
|
|
// demonstrate that input.commitment == referenced output.commitment
|
|
// based on the public key and amount begin spent
|
|
fn output_commitment_equals_input_commitment_on_spend() {
|
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
|
let builder = ProofBuilder::new(&keychain);
|
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
|
|
|
let tx1 = build::transaction(
|
|
KernelFeatures::Plain { fee: 0 },
|
|
vec![build::output(105, key_id1.clone())],
|
|
&keychain,
|
|
&builder,
|
|
)
|
|
.unwrap();
|
|
let tx2 = build::transaction(
|
|
KernelFeatures::Plain { fee: 0 },
|
|
vec![build::input(105, key_id1.clone())],
|
|
&keychain,
|
|
&builder,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(tx1.outputs()[0].features, tx2.inputs()[0].features);
|
|
assert_eq!(tx1.outputs()[0].commitment(), tx2.inputs()[0].commitment());
|
|
}
|
|
|
|
#[test]
|
|
fn payment_proof_construction() {
|
|
let secp_inst = static_secp_instance();
|
|
let secp = secp_inst.lock();
|
|
let mut test_rng = StepRng::new(1234567890u64, 1);
|
|
let sec_key = secp::key::SecretKey::new(&secp, &mut test_rng);
|
|
let d_skey = DalekSecretKey::from_bytes(&sec_key.0).unwrap();
|
|
|
|
let address: DalekPublicKey = (&d_skey).into();
|
|
|
|
let kernel_excess = {
|
|
ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
|
let keychain = ExtKeychain::from_random_seed(true).unwrap();
|
|
let switch = &SwitchCommitmentType::Regular;
|
|
let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
|
let id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
|
let skey1 = keychain.derive_key(0, &id1, switch).unwrap();
|
|
let skey2 = keychain.derive_key(0, &id2, switch).unwrap();
|
|
let blinding_factor = keychain
|
|
.blind_sum(
|
|
&BlindSum::new()
|
|
.sub_blinding_factor(BlindingFactor::from_secret_key(skey1))
|
|
.add_blinding_factor(BlindingFactor::from_secret_key(skey2)),
|
|
)
|
|
.unwrap();
|
|
keychain
|
|
.secp()
|
|
.commit(0, blinding_factor.secret_key(&keychain.secp()).unwrap())
|
|
.unwrap()
|
|
};
|
|
|
|
let amount = 12345678u64;
|
|
let msg = payment_proof_message(amount, &kernel_excess, address).unwrap();
|
|
println!("payment proof message is (len {}): {:?}", msg.len(), msg);
|
|
|
|
let decoded = _decode_payment_proof_message(&msg).unwrap();
|
|
assert_eq!(decoded.0, amount);
|
|
assert_eq!(decoded.1, kernel_excess);
|
|
assert_eq!(decoded.2, address);
|
|
|
|
let sig = create_payment_proof_signature(amount, &kernel_excess, address, sec_key).unwrap();
|
|
|
|
assert!(address.verify(&msg, &sig).is_ok());
|
|
}
|
|
}
|