mirror of
https://github.com/mimblewimble/grin-wallet.git
synced 2025-01-21 03:21:08 +03:00
Basic mwmixnet send (#696)
* integrating onion library * updates and changes to support newly included mwmixnet types * add (incorrect) owner api function * turn off test for now * switch working grin branch to master * fix doctests for build * update cargo lock in attempt to fix croaring build on CI server * update cargo lock with upstream thiserror crate * update test dependency for croaring
This commit is contained in:
parent
008d2a8c9a
commit
165632b1dc
24 changed files with 2018 additions and 398 deletions
575
Cargo.lock
generated
575
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
16
Cargo.toml
16
Cargo.toml
|
@ -48,16 +48,16 @@ grin_wallet_util = { path = "./util", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../grin/core"}
|
||||
|
|
|
@ -36,14 +36,14 @@ grin_wallet_util = { path = "../util", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
|
|
|
@ -31,6 +31,7 @@ use crate::libwallet::api_impl::{owner, owner_updater};
|
|||
use crate::libwallet::contract::types::{
|
||||
ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI,
|
||||
};
|
||||
use crate::libwallet::mwmixnet::types::{MixnetReqCreationParams, SwapReq};
|
||||
use crate::libwallet::{
|
||||
AcctPathMapping, BuiltOutput, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient,
|
||||
NodeHeightResult, OutputCommitMapping, PaymentProof, Slate, Slatepack, SlatepackAddress,
|
||||
|
@ -829,6 +830,19 @@ where
|
|||
owner::contract_revoke(&mut **w, keychain_mask, &args)
|
||||
}
|
||||
|
||||
/// Create MXMixnet request
|
||||
pub fn create_mwmixnet_req(
|
||||
&self,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
params: &MixnetReqCreationParams,
|
||||
slate: &Slate,
|
||||
// use_test_rng: bool,
|
||||
) -> Result<SwapReq, Error> {
|
||||
let mut w_lock = self.wallet_inst.lock();
|
||||
let w = w_lock.lc_provider()?.wallet_inst()?;
|
||||
owner::create_mwmixnet_req(&mut **w, keychain_mask, params, slate)
|
||||
}
|
||||
|
||||
/// Processes an invoice tranaction created by another party, essentially
|
||||
/// a `request for payment`. The incoming slate should contain a requested
|
||||
/// amount, an output created by the invoicer convering the amount, and
|
||||
|
|
|
@ -26,12 +26,12 @@ grin_wallet_util = { path = "../util", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
|
|
|
@ -46,16 +46,16 @@ grin_wallet_config = { path = "../config", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
|
@ -76,10 +76,10 @@ remove_dir_all = "0.7"
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_chain = { path = "../../grin/chain"}
|
||||
|
|
156
controller/tests/contract_srs_mwmixnet.rs
Normal file
156
controller/tests/contract_srs_mwmixnet.rs
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2022 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.
|
||||
|
||||
//! Test a wallet doing contract SRS flow
|
||||
// #[macro_use]
|
||||
extern crate grin_wallet_controller as wallet;
|
||||
extern crate grin_wallet_impls as impls;
|
||||
extern crate log;
|
||||
|
||||
use grin_wallet_libwallet as libwallet;
|
||||
|
||||
use impls::test_framework::{self};
|
||||
use libwallet::contract::my_fee_contribution;
|
||||
use libwallet::contract::types::{ContractNewArgsAPI, ContractSetupArgsAPI};
|
||||
use libwallet::mwmixnet::onion::crypto::secp;
|
||||
use libwallet::mwmixnet::types::MixnetReqCreationParams;
|
||||
use libwallet::{Slate, SlateState, TxLogEntryType};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
use common::{clean_output_dir, create_wallets, setup};
|
||||
|
||||
/// contract SRS flow - just creating an mwmixnet tx at the moment
|
||||
fn contract_srs_mwmixnet_tx_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
|
||||
// create two wallets and mine 4 blocks in each (we want both to have balance to get a payjoin)
|
||||
let (wallets, chain, stopper, mut bh) =
|
||||
create_wallets(vec![vec![("default", 4)], vec![("default", 4)]], test_dir).unwrap();
|
||||
let send_wallet = wallets[0].0.clone();
|
||||
let send_mask = wallets[0].1.as_ref();
|
||||
let recv_wallet = wallets[1].0.clone();
|
||||
let recv_mask = wallets[1].1.as_ref();
|
||||
|
||||
let mut slate = Slate::blank(0, true); // this gets overriden below
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
// Send wallet inititates a standard transaction with --send=5
|
||||
let args = &mut ContractNewArgsAPI {
|
||||
setup_args: ContractSetupArgsAPI {
|
||||
net_change: Some(-5_000_000_000),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
slate = api.contract_new(m, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard1);
|
||||
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
// Receive wallet calls --receive=5
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
net_change: Some(5_000_000_000),
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard2);
|
||||
|
||||
// Send wallet finalizes and posts
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let args = &mut ContractSetupArgsAPI {
|
||||
..Default::default()
|
||||
};
|
||||
args.proof_args.suppress_proof = true;
|
||||
slate = api.contract_sign(m, &slate, args)?;
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(slate.state, SlateState::Standard3);
|
||||
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let server_key_1 = secp::random_secret();
|
||||
let server_key_2 = secp::random_secret();
|
||||
let params = MixnetReqCreationParams {
|
||||
server_keys: vec![server_key_1, server_key_2],
|
||||
fee_per_hop: 50_000_000,
|
||||
};
|
||||
//api.create_mwmixnet_req(send_mask, ¶ms, &slate)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
bh += 1;
|
||||
|
||||
/*
|
||||
let _ =
|
||||
test_framework::award_blocks_to_wallet(&chain, send_wallet.clone(), send_mask, 3, false);
|
||||
bh += 3;
|
||||
|
||||
// Assert changes in receive wallet
|
||||
wallet::controller::owner_single_use(Some(recv_wallet.clone()), recv_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), 5); // 4 mined and 1 received
|
||||
let tx_log = txs[4].clone();
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxReceived);
|
||||
assert_eq!(tx_log.amount_credited, 5_000_000_000);
|
||||
assert_eq!(tx_log.amount_debited, 0);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
let expected_fees_paid = Some(my_fee_contribution(1, 1, 1, 2)?);
|
||||
assert_eq!(tx_log.fee, expected_fees_paid);
|
||||
assert_eq!(
|
||||
wallet_info.amount_currently_spendable,
|
||||
4 * 60_000_000_000 + 5_000_000_000 - expected_fees_paid.unwrap().fee() // we expect the balance of 4 mined blocks + 5 Grin - fees paid
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Assert changes in send wallet
|
||||
wallet::controller::owner_single_use(Some(send_wallet.clone()), send_mask, None, |api, m| {
|
||||
let (_, wallet_info) = api.retrieve_summary_info(m, true, 1)?;
|
||||
let (refreshed, txs) = api.retrieve_txs(m, true, None, None, None)?;
|
||||
assert_eq!(wallet_info.last_confirmed_height, bh);
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len() as u64, bh - 4 + 1); // send wallet didn't mine 4 blocks and made 1 tx
|
||||
let tx_log = txs[txs.len() - 5].clone(); // TODO: why -5 and not -4?
|
||||
assert_eq!(tx_log.tx_type, TxLogEntryType::TxSent);
|
||||
assert_eq!(tx_log.amount_credited, 0);
|
||||
assert_eq!(tx_log.amount_debited, 5_000_000_000);
|
||||
assert_eq!(tx_log.num_inputs, 1);
|
||||
assert_eq!(tx_log.num_outputs, 1);
|
||||
assert_eq!(tx_log.fee, Some(my_fee_contribution(1, 1, 1, 2)?));
|
||||
Ok(())
|
||||
})?;*/
|
||||
|
||||
// let logging finish
|
||||
stopper.store(false, Ordering::Relaxed);
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_contract_srs_mwmixnet_tx() -> Result<(), libwallet::Error> {
|
||||
let test_dir = "test_output/contract_srs_mwmixnet_tx";
|
||||
setup(test_dir);
|
||||
contract_srs_mwmixnet_tx_impl(test_dir)?;
|
||||
clean_output_dir(test_dir);
|
||||
Ok(())
|
||||
}
|
|
@ -52,20 +52,20 @@ grin_wallet_libwallet = { path = "../libwallet", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_chain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_chain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_api = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
|
|
|
@ -24,11 +24,11 @@ lazy_static = "1"
|
|||
strum = "0.18"
|
||||
strum_macros = "0.18"
|
||||
thiserror = "1"
|
||||
ed25519-dalek = "1.0.0-pre.4"
|
||||
ed25519-dalek = "1.0.1"
|
||||
x25519-dalek = "0.6"
|
||||
base64 = "0.9"
|
||||
regex = "1.3"
|
||||
sha2 = "0.8"
|
||||
sha2 = "0.10.0"
|
||||
bs58 = "0.3"
|
||||
age = "0.7"
|
||||
curve25519-dalek = "2.1"
|
||||
|
@ -37,6 +37,12 @@ bech32 = "0.7"
|
|||
byteorder = "1.3"
|
||||
num-bigint = "0.2"
|
||||
|
||||
#mwmixnet onion
|
||||
chacha20 = "0.8.1"
|
||||
hmac = { version = "0.12.0", features = ["std"]}
|
||||
|
||||
grin_secp256k1zkp = { version = "0.7.12", features = ["bullet-proof-sizing"]}
|
||||
|
||||
grin_wallet_util = { path = "../util", version = "5.2.0-beta.1" }
|
||||
grin_wallet_config = { path = "../config", version = "5.2.0-beta.1" }
|
||||
|
||||
|
@ -49,16 +55,16 @@ grin_wallet_config = { path = "../config", version = "5.2.0-beta.1" }
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3"}
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
# grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_core = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_keychain = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_store = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
# grin_core = { path = "../../grin/core"}
|
||||
|
@ -66,4 +72,8 @@ grin_store = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.
|
|||
# grin_util = { path = "../../grin/util"}
|
||||
# grin_store = { path = "../../grin/store"}
|
||||
|
||||
# mw-mixnet
|
||||
|
||||
|
||||
|
||||
#####
|
||||
|
|
|
@ -22,13 +22,18 @@ use crate::grin_core::core::{Output, OutputFeatures, Transaction};
|
|||
use crate::grin_core::libtx::proof;
|
||||
use crate::grin_keychain::ViewKey;
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use crate::grin_util::Mutex;
|
||||
use crate::grin_util::ToHex;
|
||||
use crate::grin_util::secp::pedersen;
|
||||
use crate::grin_util::{static_secp_instance, Mutex, ToHex};
|
||||
use crate::util::{OnionV3Address, OnionV3AddressError};
|
||||
|
||||
use crate::api_impl::owner_updater::StatusMessage;
|
||||
use crate::contract::types::{ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI};
|
||||
use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType};
|
||||
use crate::mwmixnet::onion::create_onion;
|
||||
use crate::mwmixnet::types::{
|
||||
add_excess, new_hop, random_secret, ComSignature, Hop, MixnetReqCreationParams, SwapReq,
|
||||
};
|
||||
|
||||
use crate::internal::{keys, scan, selection, tx, updater};
|
||||
use crate::slate::{PaymentInfo, Slate, SlateState};
|
||||
use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo};
|
||||
|
@ -1654,3 +1659,62 @@ where
|
|||
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
|
||||
slate.find_index_matching_context(&keychain, &context)
|
||||
}
|
||||
|
||||
/// Create MXMixnet request
|
||||
pub fn create_mwmixnet_req<'a, T: ?Sized, C, K>(
|
||||
w: &mut T,
|
||||
keychain_mask: Option<&SecretKey>,
|
||||
params: &MixnetReqCreationParams,
|
||||
slate: &Slate,
|
||||
// use_test_rng: bool,
|
||||
) -> Result<SwapReq, Error>
|
||||
where
|
||||
T: WalletBackend<'a, C, K>,
|
||||
C: NodeClient + 'a,
|
||||
K: Keychain + 'a,
|
||||
{
|
||||
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
|
||||
|
||||
let my_keys = context.get_private_keys();
|
||||
let kernel = slate.tx_or_err()?.kernels()[0];
|
||||
|
||||
let msg = kernel.msg_to_sign()?;
|
||||
|
||||
let comsig = ComSignature::sign(slate.amount, &my_keys.0, &msg.to_hex().as_bytes().to_vec())?;
|
||||
|
||||
let mut hops: Vec<Hop> = Vec::new();
|
||||
let mut final_commit = kernel.excess.clone();
|
||||
let mut final_blind = my_keys.0.clone();
|
||||
|
||||
for i in 0..params.server_keys.len() {
|
||||
let excess = params.server_keys[i].clone();
|
||||
|
||||
let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
|
||||
final_blind.add_assign(&secp, &excess).unwrap();
|
||||
final_commit = add_excess(&final_commit, &excess).unwrap();
|
||||
let proof = if i == params.server_keys.len() - 1 {
|
||||
let n1 = random_secret();
|
||||
let rp = secp.bullet_proof(
|
||||
slate.amount - (params.fee_per_hop * params.server_keys.len() as u32) as u64,
|
||||
final_blind.clone(),
|
||||
n1.clone(),
|
||||
n1.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok());
|
||||
Some(rp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hop = new_hop(¶ms.server_keys[i], &excess, params.fee_per_hop, proof);
|
||||
hops.push(hop);
|
||||
}
|
||||
|
||||
let onion = create_onion(&kernel.excess, &hops)?;
|
||||
|
||||
Ok(SwapReq { comsig, onion })
|
||||
|
||||
//slate.find_index_matching_context(&keychain, &context)
|
||||
}
|
||||
|
|
|
@ -65,6 +65,14 @@ pub enum Error {
|
|||
#[error("Onion V3 Address Error: {0}")]
|
||||
OnionV3Address(#[from] util::OnionV3AddressError),
|
||||
|
||||
/// Comsig error
|
||||
#[error("Comsig error: {0}")]
|
||||
ComSig(#[from] crate::mwmixnet::onion::crypto::comsig::ComSigError),
|
||||
|
||||
/// MwMixnet Onion error
|
||||
#[error("Onion error: {0}")]
|
||||
Onion(#[from] crate::mwmixnet::onion::onion::OnionError),
|
||||
|
||||
/// Callback implementation error conversion
|
||||
#[error("Trait Implementation error")]
|
||||
CallbackImpl(&'static str),
|
||||
|
|
|
@ -51,6 +51,8 @@ mod internal;
|
|||
mod slate;
|
||||
pub mod slate_versions;
|
||||
pub mod slatepack;
|
||||
|
||||
pub mod mwmixnet;
|
||||
mod types;
|
||||
|
||||
pub use crate::error::Error;
|
||||
|
|
17
libwallet/src/mwmixnet/mod.rs
Normal file
17
libwallet/src/mwmixnet/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Onion modules for mxmixnet
|
||||
pub mod onion;
|
||||
pub mod types;
|
210
libwallet/src/mwmixnet/onion/crypto/comsig.rs
Normal file
210
libwallet/src/mwmixnet/onion/crypto/comsig.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Comsig modules for mxmixnet
|
||||
|
||||
use secp256k1zkp::{self, pedersen::Commitment, ContextFlag, Secp256k1, SecretKey};
|
||||
|
||||
use blake2_rfc::blake2b::Blake2b;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use secp256k1zkp::rand::thread_rng;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A generalized Schnorr signature with a pedersen commitment value & blinding factors as the keys
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComSignature {
|
||||
pub_nonce: Commitment,
|
||||
s: SecretKey,
|
||||
t: SecretKey,
|
||||
}
|
||||
|
||||
/// Error types for Commitment Signatures
|
||||
#[derive(Error, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum ComSigError {
|
||||
/// Invalid com sig
|
||||
#[error("Commitment signature is invalid")]
|
||||
InvalidSig,
|
||||
/// SECP Error Wrapper
|
||||
#[error("Secp256k1zkp error: {0:?}")]
|
||||
Secp256k1zkp(secp256k1zkp::Error),
|
||||
}
|
||||
|
||||
impl From<secp256k1zkp::Error> for ComSigError {
|
||||
fn from(err: secp256k1zkp::Error) -> ComSigError {
|
||||
ComSigError::Secp256k1zkp(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComSignature {
|
||||
/// Create new Com signature from commit and keys
|
||||
pub fn new(pub_nonce: &Commitment, s: &SecretKey, t: &SecretKey) -> ComSignature {
|
||||
ComSignature {
|
||||
pub_nonce: pub_nonce.to_owned(),
|
||||
s: s.to_owned(),
|
||||
t: t.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Sign com signature with kernel values
|
||||
pub fn sign(
|
||||
amount: u64,
|
||||
blind: &SecretKey,
|
||||
msg: &Vec<u8>,
|
||||
) -> Result<ComSignature, ComSigError> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
|
||||
let mut amt_bytes = [0; 32];
|
||||
BigEndian::write_u64(&mut amt_bytes[24..32], amount);
|
||||
let k_amt = SecretKey::from_slice(&secp, &amt_bytes)?;
|
||||
|
||||
let k_1 = SecretKey::new(&secp, &mut thread_rng());
|
||||
let k_2 = SecretKey::new(&secp, &mut thread_rng());
|
||||
|
||||
let commitment = secp.commit(amount, blind.clone())?;
|
||||
let nonce_commitment = secp.commit_blind(k_1.clone(), k_2.clone())?;
|
||||
|
||||
let e = ComSignature::calc_challenge(&secp, &commitment, &nonce_commitment, &msg)?;
|
||||
|
||||
// s = k_1 + (e * amount)
|
||||
let mut s = k_amt.clone();
|
||||
s.mul_assign(&secp, &e)?;
|
||||
s.add_assign(&secp, &k_1)?;
|
||||
|
||||
// t = k_2 + (e * blind)
|
||||
let mut t = blind.clone();
|
||||
t.mul_assign(&secp, &e)?;
|
||||
t.add_assign(&secp, &k_2)?;
|
||||
|
||||
Ok(ComSignature::new(&nonce_commitment, &s, &t))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
/// Verify a com sig
|
||||
pub fn verify(&self, commit: &Commitment, msg: &Vec<u8>) -> Result<(), ComSigError> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
|
||||
let S1 = secp.commit_blind(self.s.clone(), self.t.clone())?;
|
||||
|
||||
let mut Ce = commit.to_pubkey(&secp)?;
|
||||
let e = ComSignature::calc_challenge(&secp, &commit, &self.pub_nonce, &msg)?;
|
||||
Ce.mul_assign(&secp, &e)?;
|
||||
|
||||
let commits = vec![Commitment::from_pubkey(&secp, &Ce)?, self.pub_nonce.clone()];
|
||||
let S2 = secp.commit_sum(commits, Vec::new())?;
|
||||
|
||||
if S1 != S2 {
|
||||
return Err(ComSigError::InvalidSig);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calc_challenge(
|
||||
secp: &Secp256k1,
|
||||
commit: &Commitment,
|
||||
nonce_commit: &Commitment,
|
||||
msg: &Vec<u8>,
|
||||
) -> Result<SecretKey, ComSigError> {
|
||||
let mut challenge_hasher = Blake2b::new(32);
|
||||
challenge_hasher.update(&commit.0);
|
||||
challenge_hasher.update(&nonce_commit.0);
|
||||
challenge_hasher.update(msg);
|
||||
|
||||
let mut challenge = [0; 32];
|
||||
challenge.copy_from_slice(challenge_hasher.finalize().as_bytes());
|
||||
|
||||
Ok(SecretKey::from_slice(&secp, &challenge)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes a ComSignature to and from hex
|
||||
pub mod comsig_serde {
|
||||
use super::ComSignature;
|
||||
use grin_core::ser::{self, ProtocolVersion};
|
||||
use grin_util::ToHex;
|
||||
use serde::{Deserialize, Serializer};
|
||||
|
||||
/// Serializes a ComSignature as a hex string
|
||||
pub fn serialize<S>(comsig: &ComSignature, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
use serde::ser::Error;
|
||||
let bytes = ser::ser_vec(&comsig, ProtocolVersion::local()).map_err(Error::custom)?;
|
||||
serializer.serialize_str(&bytes.to_hex())
|
||||
}
|
||||
|
||||
/// Creates a ComSignature from a hex string
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<ComSignature, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
let bytes = String::deserialize(deserializer)
|
||||
.and_then(|string| grin_util::from_hex(&string).map_err(Error::custom))?;
|
||||
let sig: ComSignature = ser::deserialize_default(&mut &bytes[..]).map_err(Error::custom)?;
|
||||
Ok(sig)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl Readable for ComSignature {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
|
||||
let R = Commitment::read(reader)?;
|
||||
let s = super::secp::read_secret_key(reader)?;
|
||||
let t = super::secp::read_secret_key(reader)?;
|
||||
Ok(ComSignature::new(&R, &s, &t))
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for ComSignature {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_fixed_bytes(self.pub_nonce.0)?;
|
||||
writer.write_fixed_bytes(self.s.0)?;
|
||||
writer.write_fixed_bytes(self.t.0)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ComSigError, ComSignature, ContextFlag, Secp256k1, SecretKey};
|
||||
|
||||
use rand::Rng;
|
||||
use secp256k1zkp::rand::{thread_rng, RngCore};
|
||||
|
||||
/// Test signing and verification of ComSignatures
|
||||
#[test]
|
||||
fn verify_comsig() -> Result<(), ComSigError> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
|
||||
let amount = thread_rng().next_u64();
|
||||
let blind = SecretKey::new(&secp, &mut thread_rng());
|
||||
let msg: [u8; 16] = rand::thread_rng().gen();
|
||||
let comsig = ComSignature::sign(amount, &blind, &msg.to_vec())?;
|
||||
|
||||
let commit = secp.commit(amount, blind.clone())?;
|
||||
assert!(comsig.verify(&commit, &msg.to_vec()).is_ok());
|
||||
|
||||
let wrong_msg: [u8; 16] = rand::thread_rng().gen();
|
||||
assert!(comsig.verify(&commit, &wrong_msg.to_vec()).is_err());
|
||||
|
||||
let wrong_commit = secp.commit(amount, SecretKey::new(&secp, &mut thread_rng()))?;
|
||||
assert!(comsig.verify(&wrong_commit, &msg.to_vec()).is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
293
libwallet/src/mwmixnet/onion/crypto/dalek.rs
Normal file
293
libwallet/src/mwmixnet/onion/crypto/dalek.rs
Normal file
|
@ -0,0 +1,293 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Dalek key wrapper for mwmixnet primitives
|
||||
|
||||
use super::secp::SecretKey;
|
||||
|
||||
use ed25519_dalek::{Keypair, PublicKey, Signature, Signer, Verifier};
|
||||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use grin_util::ToHex;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error types for Dalek structures and logic
|
||||
#[derive(Clone, Error, Debug, PartialEq)]
|
||||
pub enum DalekError {
|
||||
/// Hex deser error
|
||||
#[error("Hex error {0:?}")]
|
||||
HexError(String),
|
||||
/// Key parsing error
|
||||
#[error("Failed to parse secret key")]
|
||||
KeyParseError,
|
||||
/// Error validating signature
|
||||
#[error("Failed to verify signature")]
|
||||
SigVerifyFailed,
|
||||
}
|
||||
|
||||
/// Encapsulates an ed25519_dalek::PublicKey and provides (de-)serialization
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DalekPublicKey(PublicKey);
|
||||
|
||||
impl DalekPublicKey {
|
||||
/// Convert DalekPublicKey to hex string
|
||||
pub fn to_hex(&self) -> String {
|
||||
self.0.to_hex()
|
||||
}
|
||||
|
||||
/// Convert hex string to DalekPublicKey.
|
||||
pub fn from_hex(hex: &str) -> Result<Self, DalekError> {
|
||||
let bytes = grin_util::from_hex(hex)
|
||||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
|
||||
let pk = PublicKey::from_bytes(bytes.as_ref())
|
||||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
|
||||
Ok(DalekPublicKey(pk))
|
||||
}
|
||||
|
||||
/// Compute DalekPublicKey from a SecretKey
|
||||
pub fn from_secret(key: &SecretKey) -> Self {
|
||||
let secret = ed25519_dalek::SecretKey::from_bytes(&key.0).unwrap();
|
||||
let pk: PublicKey = (&secret).into();
|
||||
DalekPublicKey(pk)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<PublicKey> for DalekPublicKey {
|
||||
fn as_ref(&self) -> &PublicKey {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an Option<DalekPublicKey> to and from hex
|
||||
pub mod option_dalek_pubkey_serde {
|
||||
use super::DalekPublicKey;
|
||||
use grin_util::ToHex;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
///
|
||||
pub fn serialize<S>(pk: &Option<DalekPublicKey>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match pk {
|
||||
Some(pk) => serializer.serialize_str(&pk.0.to_hex()),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DalekPublicKey>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Option::<String>::deserialize(deserializer).and_then(|res| match res {
|
||||
Some(string) => DalekPublicKey::from_hex(&string)
|
||||
.map_err(|e| Error::custom(e.to_string()))
|
||||
.and_then(|pk: DalekPublicKey| Ok(Some(pk))),
|
||||
None => Ok(None),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for DalekPublicKey {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
|
||||
let pk = PublicKey::from_bytes(&reader.read_fixed_bytes(32)?)
|
||||
.map_err(|_| ser::Error::CorruptedData)?;
|
||||
Ok(DalekPublicKey(pk))
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for DalekPublicKey {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_fixed_bytes(self.0.to_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulates an ed25519_dalek::Signature and provides (de-)serialization
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DalekSignature(Signature);
|
||||
|
||||
impl DalekSignature {
|
||||
/// Convert hex string to DalekSignature.
|
||||
pub fn from_hex(hex: &str) -> Result<Self, DalekError> {
|
||||
let bytes = grin_util::from_hex(hex)
|
||||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
|
||||
let sig = Signature::from_bytes(bytes.as_ref())
|
||||
.map_err(|_| DalekError::HexError(format!("failed to decode {}", hex)))?;
|
||||
Ok(DalekSignature(sig))
|
||||
}
|
||||
|
||||
/// Verifies DalekSignature
|
||||
pub fn verify(&self, pk: &DalekPublicKey, msg: &[u8]) -> Result<(), DalekError> {
|
||||
pk.as_ref()
|
||||
.verify(&msg, &self.0)
|
||||
.map_err(|_| DalekError::SigVerifyFailed)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Signature> for DalekSignature {
|
||||
fn as_ref(&self) -> &Signature {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes a DalekSignature to and from hex
|
||||
pub mod dalek_sig_serde {
|
||||
use super::DalekSignature;
|
||||
use grin_util::ToHex;
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
///
|
||||
pub fn serialize<S>(sig: &DalekSignature, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&sig.0.to_hex())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<DalekSignature, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = String::deserialize(deserializer)?;
|
||||
let sig = DalekSignature::from_hex(&str).map_err(|e| Error::custom(e.to_string()))?;
|
||||
Ok(sig)
|
||||
}
|
||||
}
|
||||
|
||||
/// Dalek signature sign wrapper
|
||||
// TODO: This is likely duplicated throughout crate, check
|
||||
pub fn sign(sk: &SecretKey, message: &[u8]) -> Result<DalekSignature, DalekError> {
|
||||
let secret =
|
||||
ed25519_dalek::SecretKey::from_bytes(&sk.0).map_err(|_| DalekError::KeyParseError)?;
|
||||
let public: PublicKey = (&secret).into();
|
||||
let keypair = Keypair { secret, public };
|
||||
let sig = keypair.sign(&message);
|
||||
Ok(DalekSignature(sig))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mwmixnet::onion::test_util::rand_keypair;
|
||||
use grin_core::ser::{self, ProtocolVersion};
|
||||
use grin_util::ToHex;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
struct TestPubKeySerde {
|
||||
#[serde(with = "option_dalek_pubkey_serde", default)]
|
||||
pk: Option<DalekPublicKey>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pubkey_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Test from_hex
|
||||
let rand_pk = rand_keypair().1;
|
||||
let pk_from_hex = DalekPublicKey::from_hex(rand_pk.0.to_hex().as_str()).unwrap();
|
||||
assert_eq!(rand_pk.0, pk_from_hex.0);
|
||||
|
||||
// Test ser (de-)serialization
|
||||
let bytes = ser::ser_vec(&rand_pk, ProtocolVersion::local()).unwrap();
|
||||
assert_eq!(bytes.len(), 32);
|
||||
let pk_from_deser: DalekPublicKey = ser::deserialize_default(&mut &bytes[..]).unwrap();
|
||||
assert_eq!(rand_pk.0, pk_from_deser.0);
|
||||
|
||||
// Test serde with Some(rand_pk)
|
||||
let some = TestPubKeySerde {
|
||||
pk: Some(rand_pk.clone()),
|
||||
};
|
||||
let val = serde_json::to_value(some.clone()).unwrap();
|
||||
if let Value::Object(o) = &val {
|
||||
if let Value::String(s) = o.get("pk").unwrap() {
|
||||
assert_eq!(s, &rand_pk.0.to_hex());
|
||||
} else {
|
||||
panic!("Invalid type");
|
||||
}
|
||||
} else {
|
||||
panic!("Invalid type")
|
||||
}
|
||||
assert_eq!(some, serde_json::from_value(val).unwrap());
|
||||
|
||||
// Test serde with empty pk field
|
||||
let none = TestPubKeySerde { pk: None };
|
||||
let val = serde_json::to_value(none.clone()).unwrap();
|
||||
if let Value::Object(o) = &val {
|
||||
if let Value::Null = o.get("pk").unwrap() {
|
||||
// ok
|
||||
} else {
|
||||
panic!("Invalid type");
|
||||
}
|
||||
} else {
|
||||
panic!("Invalid type")
|
||||
}
|
||||
assert_eq!(none, serde_json::from_value(val).unwrap());
|
||||
|
||||
// Test serde with no pk field
|
||||
let none2 = serde_json::from_str::<TestPubKeySerde>("{}").unwrap();
|
||||
assert_eq!(none, none2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
|
||||
struct TestSigSerde {
|
||||
#[serde(with = "dalek_sig_serde")]
|
||||
sig: DalekSignature,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sig_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Sign a message
|
||||
let (sk, pk) = rand_keypair();
|
||||
let msg: [u8; 16] = rand::thread_rng().gen();
|
||||
let sig = sign(&sk, &msg).unwrap();
|
||||
|
||||
// Verify signature
|
||||
assert!(sig.verify(&pk, &msg).is_ok());
|
||||
|
||||
// Wrong message
|
||||
let wrong_msg: [u8; 16] = rand::thread_rng().gen();
|
||||
assert!(sig.verify(&pk, &wrong_msg).is_err());
|
||||
|
||||
// Wrong pubkey
|
||||
let wrong_pk = rand_keypair().1;
|
||||
assert!(sig.verify(&wrong_pk, &msg).is_err());
|
||||
|
||||
// Test from_hex
|
||||
let sig_from_hex = DalekSignature::from_hex(sig.0.to_hex().as_str()).unwrap();
|
||||
assert_eq!(sig.0, sig_from_hex.0);
|
||||
|
||||
// Test serde (de-)serialization
|
||||
let serde_test = TestSigSerde { sig: sig.clone() };
|
||||
let val = serde_json::to_value(serde_test.clone()).unwrap();
|
||||
if let Value::Object(o) = &val {
|
||||
if let Value::String(s) = o.get("sig").unwrap() {
|
||||
assert_eq!(s, &sig.0.to_hex());
|
||||
} else {
|
||||
panic!("Invalid type");
|
||||
}
|
||||
} else {
|
||||
panic!("Invalid type")
|
||||
}
|
||||
assert_eq!(serde_test, serde_json::from_value(val).unwrap());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
19
libwallet/src/mwmixnet/onion/crypto/mod.rs
Normal file
19
libwallet/src/mwmixnet/onion/crypto/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Onion and comsig modules for mxmixnet
|
||||
|
||||
pub mod comsig;
|
||||
pub mod dalek;
|
||||
pub mod secp;
|
79
libwallet/src/mwmixnet/onion/crypto/secp.rs
Normal file
79
libwallet/src/mwmixnet/onion/crypto/secp.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! SECP wrapper functions for onion/comsig
|
||||
//! TODO: Likely redundant stuff in here, trim
|
||||
|
||||
pub use secp256k1zkp::aggsig;
|
||||
pub use secp256k1zkp::constants::{
|
||||
AGG_SIGNATURE_SIZE, COMPRESSED_PUBLIC_KEY_SIZE, MAX_PROOF_SIZE, PEDERSEN_COMMITMENT_SIZE,
|
||||
SECRET_KEY_SIZE,
|
||||
};
|
||||
pub use secp256k1zkp::ecdh::SharedSecret;
|
||||
pub use secp256k1zkp::key::{PublicKey, SecretKey, ZERO_KEY};
|
||||
pub use secp256k1zkp::pedersen::{Commitment, RangeProof};
|
||||
pub use secp256k1zkp::{ContextFlag, Message, Secp256k1, Signature};
|
||||
|
||||
use grin_core::ser::{self, Reader};
|
||||
use secp256k1zkp::rand::thread_rng;
|
||||
|
||||
/// Generate a random SecretKey.
|
||||
pub fn random_secret() -> SecretKey {
|
||||
let secp = Secp256k1::new();
|
||||
SecretKey::new(&secp, &mut thread_rng())
|
||||
}
|
||||
|
||||
/// Deserialize a SecretKey from a Reader
|
||||
pub fn read_secret_key<R: Reader>(reader: &mut R) -> Result<SecretKey, ser::Error> {
|
||||
let buf = reader.read_fixed_bytes(SECRET_KEY_SIZE)?;
|
||||
let secp = Secp256k1::with_caps(ContextFlag::None);
|
||||
let pk = SecretKey::from_slice(&secp, &buf).map_err(|_| ser::Error::CorruptedData)?;
|
||||
Ok(pk)
|
||||
}
|
||||
|
||||
/// Build a Pedersen Commitment using the provided value and blinding factor
|
||||
pub fn commit(value: u64, blind: &SecretKey) -> Result<Commitment, secp256k1zkp::Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
let commit = secp.commit(value, blind.clone())?;
|
||||
Ok(commit)
|
||||
}
|
||||
|
||||
/// Add a blinding factor to an existing Commitment
|
||||
pub fn add_excess(
|
||||
commitment: &Commitment,
|
||||
excess: &SecretKey,
|
||||
) -> Result<Commitment, secp256k1zkp::Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
let excess_commit: Commitment = secp.commit(0, excess.clone())?;
|
||||
|
||||
let commits = vec![commitment.clone(), excess_commit.clone()];
|
||||
let sum = secp.commit_sum(commits, Vec::new())?;
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
/// Subtracts a value (v*H) from an existing commitment
|
||||
pub fn sub_value(commitment: &Commitment, value: u64) -> Result<Commitment, secp256k1zkp::Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Commit);
|
||||
let neg_commit: Commitment = secp.commit(value, ZERO_KEY)?;
|
||||
let sum = secp.commit_sum(vec![commitment.clone()], vec![neg_commit.clone()])?;
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
/// Signs the message with the provided SecretKey
|
||||
pub fn sign(sk: &SecretKey, msg: &Message) -> Result<Signature, secp256k1zkp::Error> {
|
||||
let secp = Secp256k1::with_caps(ContextFlag::Full);
|
||||
let pubkey = PublicKey::from_secret_key(&secp, &sk)?;
|
||||
let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None, Some(&pubkey), None)?;
|
||||
Ok(sig)
|
||||
}
|
194
libwallet/src/mwmixnet/onion/mod.rs
Normal file
194
libwallet/src/mwmixnet/onion/mod.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Onion module definition
|
||||
|
||||
pub mod crypto;
|
||||
pub mod onion;
|
||||
pub mod util;
|
||||
|
||||
use crypto::secp::{random_secret, Commitment, SecretKey};
|
||||
use onion::{new_stream_cipher, Onion, OnionError, Payload, RawBytes};
|
||||
|
||||
use chacha20::cipher::StreamCipher;
|
||||
use grin_core::core::FeeFields;
|
||||
use secp256k1zkp::pedersen::RangeProof;
|
||||
use x25519_dalek::PublicKey as xPublicKey;
|
||||
use x25519_dalek::{SharedSecret, StaticSecret};
|
||||
|
||||
/// Onion hop struct
|
||||
#[derive(Clone)]
|
||||
pub struct Hop {
|
||||
/// Comsig server public key
|
||||
pub server_pubkey: xPublicKey,
|
||||
/// Kernel excess
|
||||
pub excess: SecretKey,
|
||||
/// Fee
|
||||
pub fee: FeeFields,
|
||||
/// Rangeproof
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
}
|
||||
|
||||
/// Crate a new hop
|
||||
pub fn new_hop(
|
||||
server_key: &SecretKey,
|
||||
hop_excess: &SecretKey,
|
||||
fee: u32,
|
||||
proof: Option<RangeProof>,
|
||||
) -> Hop {
|
||||
Hop {
|
||||
server_pubkey: xPublicKey::from(&StaticSecret::from(server_key.0.clone())),
|
||||
excess: hop_excess.clone(),
|
||||
fee: FeeFields::from(fee as u32),
|
||||
rangeproof: proof,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Onion for the Commitment, encrypting the payload for each hop
|
||||
pub fn create_onion(commitment: &Commitment, hops: &Vec<Hop>) -> Result<Onion, OnionError> {
|
||||
if hops.is_empty() {
|
||||
return Ok(Onion {
|
||||
ephemeral_pubkey: xPublicKey::from([0u8; 32]),
|
||||
commit: commitment.clone(),
|
||||
enc_payloads: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let mut shared_secrets: Vec<SharedSecret> = Vec::new();
|
||||
let mut enc_payloads: Vec<RawBytes> = Vec::new();
|
||||
let mut ephemeral_sk = StaticSecret::from(random_secret().0);
|
||||
let onion_ephemeral_pk = xPublicKey::from(&ephemeral_sk);
|
||||
for i in 0..hops.len() {
|
||||
let hop = &hops[i];
|
||||
let shared_secret = ephemeral_sk.diffie_hellman(&hop.server_pubkey);
|
||||
shared_secrets.push(shared_secret);
|
||||
|
||||
ephemeral_sk = StaticSecret::from(random_secret().0);
|
||||
let next_ephemeral_pk = if i < (hops.len() - 1) {
|
||||
xPublicKey::from(&ephemeral_sk)
|
||||
} else {
|
||||
xPublicKey::from([0u8; 32])
|
||||
};
|
||||
|
||||
let payload = Payload {
|
||||
next_ephemeral_pk,
|
||||
excess: hop.excess.clone(),
|
||||
fee: hop.fee.clone(),
|
||||
rangeproof: hop.rangeproof.clone(),
|
||||
};
|
||||
enc_payloads.push(payload.serialize()?);
|
||||
}
|
||||
|
||||
for i in (0..shared_secrets.len()).rev() {
|
||||
let mut cipher = new_stream_cipher(&shared_secrets[i])?;
|
||||
for j in i..shared_secrets.len() {
|
||||
cipher.apply_keystream(&mut enc_payloads[j]);
|
||||
}
|
||||
}
|
||||
|
||||
let onion = Onion {
|
||||
ephemeral_pubkey: onion_ephemeral_pk,
|
||||
commit: commitment.clone(),
|
||||
enc_payloads,
|
||||
};
|
||||
Ok(onion)
|
||||
}
|
||||
|
||||
/// Internal tests
|
||||
#[allow(missing_docs)]
|
||||
pub mod test_util {
|
||||
use super::*;
|
||||
use crypto::dalek::DalekPublicKey;
|
||||
use crypto::secp;
|
||||
|
||||
use grin_core::core::hash::Hash;
|
||||
use grin_util::ToHex;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use secp256k1zkp::Secp256k1;
|
||||
|
||||
pub fn rand_onion() -> Onion {
|
||||
let commit = rand_commit();
|
||||
let mut hops = Vec::new();
|
||||
let k = (thread_rng().next_u64() % 5) + 1;
|
||||
for i in 0..k {
|
||||
let rangeproof = if i == (k - 1) {
|
||||
Some(rand_proof())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let hop = new_hop(
|
||||
&random_secret(),
|
||||
&random_secret(),
|
||||
thread_rng().next_u32(),
|
||||
rangeproof,
|
||||
);
|
||||
hops.push(hop);
|
||||
}
|
||||
|
||||
create_onion(&commit, &hops).unwrap()
|
||||
}
|
||||
|
||||
pub fn rand_commit() -> Commitment {
|
||||
secp::commit(rand::thread_rng().next_u64(), &secp::random_secret()).unwrap()
|
||||
}
|
||||
|
||||
pub fn rand_hash() -> Hash {
|
||||
Hash::from_hex(secp::random_secret().to_hex().as_str()).unwrap()
|
||||
}
|
||||
|
||||
pub fn rand_proof() -> RangeProof {
|
||||
let secp = Secp256k1::new();
|
||||
secp.bullet_proof(
|
||||
rand::thread_rng().next_u64(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn proof(
|
||||
value: u64,
|
||||
fee: u32,
|
||||
input_blind: &SecretKey,
|
||||
hop_excesses: &Vec<&SecretKey>,
|
||||
) -> (Commitment, RangeProof) {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let mut blind = input_blind.clone();
|
||||
for hop_excess in hop_excesses {
|
||||
blind.add_assign(&secp, &hop_excess).unwrap();
|
||||
}
|
||||
|
||||
let out_value = value - (fee as u64);
|
||||
|
||||
let rp = secp.bullet_proof(
|
||||
out_value,
|
||||
blind.clone(),
|
||||
secp::random_secret(),
|
||||
secp::random_secret(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
(secp::commit(out_value, &blind).unwrap(), rp)
|
||||
}
|
||||
|
||||
pub fn rand_keypair() -> (SecretKey, DalekPublicKey) {
|
||||
let sk = random_secret();
|
||||
let pk = DalekPublicKey::from_secret(&sk);
|
||||
(sk, pk)
|
||||
}
|
||||
}
|
430
libwallet/src/mwmixnet/onion/onion.rs
Normal file
430
libwallet/src/mwmixnet/onion/onion.rs
Normal file
|
@ -0,0 +1,430 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Onion defn for mwmixnet
|
||||
|
||||
use super::crypto::secp::{self, Commitment, RangeProof, SecretKey};
|
||||
use super::util::{read_optional, vec_to_array, write_optional};
|
||||
|
||||
use chacha20::cipher::{NewCipher, StreamCipher};
|
||||
use chacha20::{ChaCha20, Key, Nonce};
|
||||
use grin_core::core::FeeFields;
|
||||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use grin_util::{self, ToHex};
|
||||
use hmac::digest::InvalidLength;
|
||||
use hmac::{Hmac, Mac};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::result::Result;
|
||||
use thiserror::Error;
|
||||
use x25519_dalek::{PublicKey as xPublicKey, SharedSecret, StaticSecret};
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
/// Wrap u8 vec
|
||||
pub type RawBytes = Vec<u8>;
|
||||
|
||||
const CURRENT_ONION_VERSION: u8 = 0;
|
||||
|
||||
/// A data packet with layers of encryption
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Onion {
|
||||
/// The onion originator's portion of the shared secret
|
||||
pub ephemeral_pubkey: xPublicKey,
|
||||
/// The pedersen commitment before adjusting the excess and subtracting the fee
|
||||
pub commit: Commitment,
|
||||
/// The encrypted payloads which represent the layers of the onion
|
||||
pub enc_payloads: Vec<RawBytes>,
|
||||
}
|
||||
|
||||
impl PartialEq for Onion {
|
||||
fn eq(&self, other: &Onion) -> bool {
|
||||
*self.ephemeral_pubkey.as_bytes() == *other.ephemeral_pubkey.as_bytes()
|
||||
&& self.commit == other.commit
|
||||
&& self.enc_payloads == other.enc_payloads
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Onion {}
|
||||
|
||||
impl Hash for Onion {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write(self.ephemeral_pubkey.as_bytes());
|
||||
state.write(self.commit.as_ref());
|
||||
state.write_usize(self.enc_payloads.len());
|
||||
for p in &self.enc_payloads {
|
||||
state.write(p.as_slice());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single, decrypted/peeled layer of an Onion.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Payload {
|
||||
/// next ephemeral pk
|
||||
pub next_ephemeral_pk: xPublicKey,
|
||||
/// excess
|
||||
pub excess: SecretKey,
|
||||
/// fee
|
||||
pub fee: FeeFields,
|
||||
/// proof
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
/// Deser a payload
|
||||
pub fn deserialize(bytes: &Vec<u8>) -> Result<Payload, ser::Error> {
|
||||
let payload: Payload = ser::deserialize_default(&mut &bytes[..])?;
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
/// Serialize a payload
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ser::Error> {
|
||||
let mut vec = vec![];
|
||||
ser::serialize_default(&mut vec, &self)?;
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for Payload {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<Payload, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_ONION_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
|
||||
let next_ephemeral_pk =
|
||||
xPublicKey::from(vec_to_array::<32>(&reader.read_fixed_bytes(32)?)?);
|
||||
let excess = secp::read_secret_key(reader)?;
|
||||
let fee = FeeFields::try_from(reader.read_u64()?).map_err(|_| ser::Error::CorruptedData)?;
|
||||
let rangeproof = read_optional(reader)?;
|
||||
Ok(Payload {
|
||||
next_ephemeral_pk,
|
||||
excess,
|
||||
fee,
|
||||
rangeproof,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Payload {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_ONION_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.next_ephemeral_pk.as_bytes())?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
write_optional(writer, &self.rangeproof)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An onion with a layer decrypted
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PeeledOnion {
|
||||
/// The payload from the peeled layer
|
||||
pub payload: Payload,
|
||||
/// The onion remaining after a layer was peeled
|
||||
pub onion: Onion,
|
||||
}
|
||||
|
||||
impl Onion {
|
||||
/// Serialize onion
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ser::Error> {
|
||||
let mut vec = vec![];
|
||||
ser::serialize_default(&mut vec, &self)?;
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
/// Peel a single layer off of the Onion, returning the peeled Onion and decrypted Payload
|
||||
pub fn peel_layer(&self, server_key: &SecretKey) -> Result<PeeledOnion, OnionError> {
|
||||
let shared_secret = StaticSecret::from(server_key.0).diffie_hellman(&self.ephemeral_pubkey);
|
||||
let mut cipher = new_stream_cipher(&shared_secret)?;
|
||||
|
||||
let mut decrypted_bytes = self.enc_payloads[0].clone();
|
||||
cipher.apply_keystream(&mut decrypted_bytes);
|
||||
let decrypted_payload = Payload::deserialize(&decrypted_bytes)
|
||||
.map_err(|e| OnionError::DeserializationError(e))?;
|
||||
|
||||
let enc_payloads: Vec<RawBytes> = self
|
||||
.enc_payloads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(i, _)| i != 0)
|
||||
.map(|(_, enc_payload)| {
|
||||
let mut p = enc_payload.clone();
|
||||
cipher.apply_keystream(&mut p);
|
||||
p
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut commitment = self.commit.clone();
|
||||
commitment = secp::add_excess(&commitment, &decrypted_payload.excess)
|
||||
.map_err(|e| OnionError::CalcCommitError(e))?;
|
||||
commitment = secp::sub_value(&commitment, decrypted_payload.fee.into())
|
||||
.map_err(|e| OnionError::CalcCommitError(e))?;
|
||||
|
||||
let peeled_onion = Onion {
|
||||
ephemeral_pubkey: decrypted_payload.next_ephemeral_pk,
|
||||
commit: commitment.clone(),
|
||||
enc_payloads,
|
||||
};
|
||||
Ok(PeeledOnion {
|
||||
payload: decrypted_payload,
|
||||
onion: peeled_onion,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new stream cypher from shared secret
|
||||
pub fn new_stream_cipher(shared_secret: &SharedSecret) -> Result<ChaCha20, OnionError> {
|
||||
let mut mu_hmac = HmacSha256::new_from_slice(b"MWIXNET")?;
|
||||
mu_hmac.update(shared_secret.as_bytes());
|
||||
let mukey = mu_hmac.finalize().into_bytes();
|
||||
|
||||
let key = Key::from_slice(&mukey[0..32]);
|
||||
let nonce = Nonce::from_slice(b"NONCE1234567");
|
||||
|
||||
Ok(ChaCha20::new(&key, &nonce))
|
||||
}
|
||||
|
||||
impl Writeable for Onion {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_fixed_bytes(self.ephemeral_pubkey.as_bytes())?;
|
||||
writer.write_fixed_bytes(&self.commit)?;
|
||||
writer.write_u64(self.enc_payloads.len() as u64)?;
|
||||
for p in &self.enc_payloads {
|
||||
writer.write_u64(p.len() as u64)?;
|
||||
p.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for Onion {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<Onion, ser::Error> {
|
||||
let pubkey_bytes: [u8; 32] = vec_to_array(&reader.read_fixed_bytes(32)?)?;
|
||||
let ephemeral_pubkey = xPublicKey::from(pubkey_bytes);
|
||||
let commit = Commitment::read(reader)?;
|
||||
let mut enc_payloads: Vec<RawBytes> = Vec::new();
|
||||
let len = reader.read_u64()?;
|
||||
for _ in 0..len {
|
||||
let size = reader.read_u64()?;
|
||||
let bytes = reader.read_fixed_bytes(size as usize)?;
|
||||
enc_payloads.push(bytes);
|
||||
}
|
||||
Ok(Onion {
|
||||
ephemeral_pubkey,
|
||||
commit,
|
||||
enc_payloads,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::Serialize for Onion {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Onion", 3)?;
|
||||
|
||||
state.serialize_field("pubkey", &self.ephemeral_pubkey.as_bytes().to_hex())?;
|
||||
state.serialize_field("commit", &self.commit.to_hex())?;
|
||||
|
||||
let hex_payloads: Vec<String> = self.enc_payloads.iter().map(|v| v.to_hex()).collect();
|
||||
state.serialize_field("data", &hex_payloads)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for Onion {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "snake_case")]
|
||||
enum Field {
|
||||
Pubkey,
|
||||
Commit,
|
||||
Data,
|
||||
}
|
||||
|
||||
struct OnionVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for OnionVisitor {
|
||||
type Value = Onion;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("an Onion")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
let mut pubkey = None;
|
||||
let mut commit = None;
|
||||
let mut data = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Pubkey => {
|
||||
let val: String = map.next_value()?;
|
||||
let vec =
|
||||
grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
|
||||
pubkey =
|
||||
Some(xPublicKey::from(vec_to_array::<32>(&vec).map_err(
|
||||
|_| serde::de::Error::custom("Invalid length pubkey"),
|
||||
)?));
|
||||
}
|
||||
Field::Commit => {
|
||||
let val: String = map.next_value()?;
|
||||
let vec =
|
||||
grin_util::from_hex(&val).map_err(serde::de::Error::custom)?;
|
||||
commit = Some(Commitment::from_vec(vec));
|
||||
}
|
||||
Field::Data => {
|
||||
let val: Vec<String> = map.next_value()?;
|
||||
let mut vec: Vec<Vec<u8>> = Vec::new();
|
||||
for hex in val {
|
||||
vec.push(
|
||||
grin_util::from_hex(&hex).map_err(serde::de::Error::custom)?,
|
||||
);
|
||||
}
|
||||
data = Some(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Onion {
|
||||
ephemeral_pubkey: pubkey.unwrap(),
|
||||
commit: commit.unwrap(),
|
||||
enc_payloads: data.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &[&str] = &["pubkey", "commit", "data"];
|
||||
deserializer.deserialize_struct("Onion", &FIELDS, OnionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error types for creating and peeling Onions
|
||||
#[derive(Clone, Error, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum OnionError {
|
||||
/// Invalid Key Length
|
||||
#[error("Invalid key length for MAC initialization")]
|
||||
InvalidKeyLength,
|
||||
/// Serialization error
|
||||
#[error("Serialization error occurred: {0:?}")]
|
||||
SerializationError(ser::Error),
|
||||
/// Deserialization error
|
||||
#[error("Deserialization error occurred: {0:?}")]
|
||||
DeserializationError(ser::Error),
|
||||
/// Error calculating blinding factor
|
||||
#[error("Error calculating blinding factor: {0:?}")]
|
||||
CalcBlindError(secp256k1zkp::Error),
|
||||
/// Error calculating ephemeral key
|
||||
#[error("Error calculating ephemeral pubkey: {0:?}")]
|
||||
CalcPubKeyError(secp256k1zkp::Error),
|
||||
/// Error calculating commitment
|
||||
#[error("Error calculating commitment: {0:?}")]
|
||||
CalcCommitError(secp256k1zkp::Error),
|
||||
}
|
||||
|
||||
impl From<InvalidLength> for OnionError {
|
||||
fn from(_err: InvalidLength) -> OnionError {
|
||||
OnionError::InvalidKeyLength
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ser::Error> for OnionError {
|
||||
fn from(err: ser::Error) -> OnionError {
|
||||
OnionError::SerializationError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::mwmixnet::onion::crypto::secp::random_secret;
|
||||
use crate::mwmixnet::onion::{new_hop, Hop};
|
||||
|
||||
use grin_core::core::FeeFields;
|
||||
|
||||
/// Test end-to-end Onion creation and unwrapping logic.
|
||||
#[test]
|
||||
fn onion() {
|
||||
let total_fee: u64 = 10;
|
||||
let fee_per_hop: u32 = 2;
|
||||
let in_value: u64 = 1000;
|
||||
let out_value: u64 = in_value - total_fee;
|
||||
let blind = random_secret();
|
||||
let commitment = secp::commit(in_value, &blind).unwrap();
|
||||
|
||||
let mut hops: Vec<Hop> = Vec::new();
|
||||
let mut keys: Vec<SecretKey> = Vec::new();
|
||||
let mut final_commit = secp::commit(out_value, &blind).unwrap();
|
||||
let mut final_blind = blind.clone();
|
||||
for i in 0..5 {
|
||||
keys.push(random_secret());
|
||||
|
||||
let excess = random_secret();
|
||||
|
||||
let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit);
|
||||
final_blind.add_assign(&secp, &excess).unwrap();
|
||||
final_commit = secp::add_excess(&final_commit, &excess).unwrap();
|
||||
let proof = if i == 4 {
|
||||
let n1 = random_secret();
|
||||
let rp = secp.bullet_proof(
|
||||
out_value,
|
||||
final_blind.clone(),
|
||||
n1.clone(),
|
||||
n1.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok());
|
||||
Some(rp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hop = new_hop(&keys[i], &excess, fee_per_hop, proof);
|
||||
hops.push(hop);
|
||||
}
|
||||
|
||||
let mut onion_packet = crate::mwmixnet::onion::create_onion(&commitment, &hops).unwrap();
|
||||
|
||||
let mut payload = Payload {
|
||||
next_ephemeral_pk: onion_packet.ephemeral_pubkey.clone(),
|
||||
excess: random_secret(),
|
||||
fee: FeeFields::from(fee_per_hop as u32),
|
||||
rangeproof: None,
|
||||
};
|
||||
for i in 0..5 {
|
||||
let peeled = onion_packet.peel_layer(&keys[i]).unwrap();
|
||||
payload = peeled.payload;
|
||||
onion_packet = peeled.onion;
|
||||
}
|
||||
|
||||
assert!(payload.rangeproof.is_some());
|
||||
assert_eq!(payload.rangeproof.unwrap(), hops[4].rangeproof.unwrap());
|
||||
assert_eq!(secp::commit(out_value, &final_blind).unwrap(), final_commit);
|
||||
assert_eq!(payload.fee, FeeFields::from(fee_per_hop as u32));
|
||||
}
|
||||
}
|
185
libwallet/src/mwmixnet/onion/util.rs
Normal file
185
libwallet/src/mwmixnet/onion/util.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2023 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.
|
||||
|
||||
//! Util fns for mwmixnet
|
||||
//! TODO: possibly redundant, check or move elsewhere
|
||||
|
||||
use grin_core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Writes an optional value as '1' + value if Some, or '0' if None
|
||||
///
|
||||
/// This function is used to serialize an optional value into a Writer. If the option
|
||||
/// contains Some value, it writes '1' followed by the serialized value. If the option
|
||||
/// is None, it just writes '0'.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `writer` - A Writer instance where the data will be written.
|
||||
/// * `o` - The Optional value that will be written.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * If successful, returns Ok with nothing.
|
||||
/// * If an error occurs during writing, returns Err wrapping the error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use grin_wallet_libwallet::mwmixnet::onion::util::write_optional;
|
||||
/// let mut writer:Vec<u8> = vec![];
|
||||
/// let optional_value: Option<u32> = Some(10);
|
||||
/// //write_optional(&mut writer, &optional_value);
|
||||
/// ```
|
||||
pub fn write_optional<O: Writeable, W: Writer>(
|
||||
writer: &mut W,
|
||||
o: &Option<O>,
|
||||
) -> Result<(), ser::Error> {
|
||||
match &o {
|
||||
Some(o) => {
|
||||
writer.write_u8(1)?;
|
||||
o.write(writer)?;
|
||||
}
|
||||
None => writer.write_u8(0)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads an optional value as '1' + value if Some, or '0' if None
|
||||
///
|
||||
/// This function is used to deserialize an optional value from a Reader. If the first byte
|
||||
/// read is '0', it returns None. If the first byte is '1', it reads the next value and
|
||||
/// returns Some(value).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reader` - A Reader instance from where the data will be read.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * If successful, returns Ok wrapping an optional value. If the first byte read was '0',
|
||||
/// returns None. If it was '1', returns Some(value).
|
||||
/// * If an error occurs during reading, returns Err wrapping the error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use grin_wallet_libwallet::mwmixnet::onion::util::read_optional;
|
||||
/// use grin_core::ser::{BinReader, ProtocolVersion, DeserializationMode};
|
||||
/// let mut buf: &[u8] = &[1, 0, 0, 0, 10];
|
||||
/// let mut reader = BinReader::new(&mut buf, ProtocolVersion::local(), DeserializationMode::default());
|
||||
/// let optional_value: Option<u32> = read_optional(&mut reader).unwrap();
|
||||
/// assert_eq!(optional_value, Some(10));
|
||||
/// ```
|
||||
pub fn read_optional<O: Readable, R: Reader>(reader: &mut R) -> Result<Option<O>, ser::Error> {
|
||||
let o = if reader.read_u8()? == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(O::read(reader)?)
|
||||
};
|
||||
Ok(o)
|
||||
}
|
||||
|
||||
/// Convert a vector to an array of size `S`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vec` - The input vector.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * If successful, returns an `Ok` wrapping an array of size `S` containing
|
||||
/// the first `S` bytes of `vec`.
|
||||
/// * If `vec` is smaller than `S`, returns an `Err` indicating a count error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use grin_wallet_libwallet::mwmixnet::onion::util::vec_to_array;
|
||||
/// let v = vec![0, 1, 2, 3, 4, 5];
|
||||
/// let a = vec_to_array::<4>(&v).unwrap();
|
||||
/// assert_eq!(a, [0, 1, 2, 3]);
|
||||
/// ```
|
||||
pub fn vec_to_array<const S: usize>(vec: &Vec<u8>) -> Result<[u8; S], ser::Error> {
|
||||
if vec.len() < S {
|
||||
return Err(ser::Error::CountError);
|
||||
}
|
||||
let arr: [u8; S] = vec[0..S].try_into().unwrap();
|
||||
Ok(arr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use grin_core::ser::{BinReader, BinWriter, DeserializationMode, ProtocolVersion};
|
||||
|
||||
#[test]
|
||||
fn test_write_optional() {
|
||||
// Test with Some value
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
let val: Option<u32> = Some(10);
|
||||
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap();
|
||||
assert_eq!(buf, &[1, 0, 0, 0, 10]); // 1 for Some, then 10 as a little-endian u32
|
||||
|
||||
// Test with None value
|
||||
buf.clear();
|
||||
let val: Option<u32> = None;
|
||||
write_optional(&mut BinWriter::default(&mut buf), &val).unwrap();
|
||||
assert_eq!(buf, &[0]); // 0 for None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_optional() {
|
||||
// Test with Some value
|
||||
let mut buf: &[u8] = &[1, 0, 0, 0, 10]; // 1 for Some, then 10 as a little-endian u32
|
||||
let val: Option<u32> = read_optional(&mut BinReader::new(
|
||||
&mut buf,
|
||||
ProtocolVersion::local(),
|
||||
DeserializationMode::default(),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(val, Some(10));
|
||||
|
||||
// Test with None value
|
||||
buf = &[0]; // 0 for None
|
||||
let val: Option<u32> = read_optional(&mut BinReader::new(
|
||||
&mut buf,
|
||||
ProtocolVersion::local(),
|
||||
DeserializationMode::default(),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(val, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_to_array_success() {
|
||||
let v = vec![1, 2, 3, 4, 5, 6, 7, 8];
|
||||
let a = vec_to_array::<4>(&v).unwrap();
|
||||
assert_eq!(a, [1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_to_array_too_small() {
|
||||
let v = vec![1, 2, 3];
|
||||
let res = vec_to_array::<4>(&v);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec_to_array_empty() {
|
||||
let v = vec![];
|
||||
let res = vec_to_array::<4>(&v);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
42
libwallet/src/mwmixnet/types.rs
Normal file
42
libwallet/src/mwmixnet/types.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2022 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.
|
||||
|
||||
//! Types related to mwmixnet requests required by rest of lib crate apis
|
||||
//! Should rexport all needed types here
|
||||
|
||||
pub use super::onion::crypto::comsig::{self, ComSignature};
|
||||
pub use super::onion::crypto::secp::{add_excess, random_secret};
|
||||
pub use super::onion::onion::Onion;
|
||||
pub use super::onion::{new_hop, Hop};
|
||||
use crate::grin_util::secp::key::SecretKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A Swap request
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SwapReq {
|
||||
/// Com signature
|
||||
#[serde(with = "comsig::comsig_serde")]
|
||||
pub comsig: ComSignature,
|
||||
/// Onion
|
||||
pub onion: Onion,
|
||||
}
|
||||
|
||||
/// MWMixnetRequest Creation Params
|
||||
|
||||
pub struct MixnetReqCreationParams {
|
||||
/// List of all the server keys
|
||||
pub server_keys: Vec<SecretKey>,
|
||||
/// Fees per hop
|
||||
pub fee_per_hop: u32,
|
||||
}
|
|
@ -189,10 +189,10 @@ fn format_slatepack(slatepack: &str) -> Result<String, Error> {
|
|||
// Returns the first four bytes of a double sha256 hash of some bytes
|
||||
fn generate_check(payload: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let mut first_hash = Sha256::new();
|
||||
first_hash.input(payload);
|
||||
first_hash.update(payload);
|
||||
let mut second_hash = Sha256::new();
|
||||
second_hash.input(first_hash.result());
|
||||
let checksum = second_hash.result();
|
||||
second_hash.update(first_hash.finalize());
|
||||
let checksum = second_hash.finalize();
|
||||
let check_bytes: Vec<u8> = checksum[0..4].to_vec();
|
||||
Ok(check_bytes)
|
||||
}
|
||||
|
|
|
@ -190,8 +190,8 @@ impl Slatepack {
|
|||
let mut b = [0u8; 32];
|
||||
b.copy_from_slice(&dec_key.as_bytes()[0..32]);
|
||||
let mut hasher = Sha512::new();
|
||||
hasher.input(b);
|
||||
let result = hasher.result();
|
||||
hasher.update(b);
|
||||
let result = hasher.finalize();
|
||||
b.copy_from_slice(&result[0..32]);
|
||||
|
||||
let x_dec_secret = StaticSecret::from(b);
|
||||
|
|
|
@ -25,10 +25,10 @@ thiserror = "1"
|
|||
|
||||
# For beta release
|
||||
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", tag = "v5.2.0-beta.3" }
|
||||
|
||||
# For bleeding edge
|
||||
# grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
grin_util = { git = "https://github.com/mimblewimble/grin", branch = "master" }
|
||||
|
||||
# For local testing
|
||||
|
||||
|
|
Loading…
Reference in a new issue