mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Merge remote-tracking branch 'upstream/master' into feature/slate-version
This commit is contained in:
commit
e505726d73
17 changed files with 684 additions and 19 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -788,6 +788,7 @@ dependencies = [
|
||||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.81 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -30,3 +30,6 @@ chrono = "0.4.4"
|
||||||
|
|
||||||
grin_keychain = { path = "../keychain", version = "1.0.0" }
|
grin_keychain = { path = "../keychain", version = "1.0.0" }
|
||||||
grin_util = { path = "../util", version = "1.0.0" }
|
grin_util = { path = "../util", version = "1.0.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1"
|
||||||
|
|
|
@ -267,17 +267,14 @@ pub fn is_production_mode() -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are we in floonet?
|
/// Are we in floonet?
|
||||||
|
/// Note: We do not have a corresponding is_mainnet() as we want any tests to be as close
|
||||||
|
/// as possible to "mainnet" configuration as possible.
|
||||||
|
/// We want to avoid missing any mainnet only code paths.
|
||||||
pub fn is_floonet() -> bool {
|
pub fn is_floonet() -> bool {
|
||||||
let param_ref = CHAIN_TYPE.read();
|
let param_ref = CHAIN_TYPE.read();
|
||||||
ChainTypes::Floonet == *param_ref
|
ChainTypes::Floonet == *param_ref
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are we for real?
|
|
||||||
pub fn is_mainnet() -> bool {
|
|
||||||
let param_ref = CHAIN_TYPE.read();
|
|
||||||
ChainTypes::Mainnet == *param_ref
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to get a nonce known to create a valid POW on
|
/// Helper function to get a nonce known to create a valid POW on
|
||||||
/// the genesis block, to prevent it taking ages. Should be fine for now
|
/// the genesis block, to prevent it taking ages. Should be fine for now
|
||||||
/// as the genesis block POW solution turns out to be the same for every new
|
/// as the genesis block POW solution turns out to be the same for every new
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub mod build;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod proof;
|
pub mod proof;
|
||||||
pub mod reward;
|
pub mod reward;
|
||||||
pub mod serialization;
|
pub mod secp_ser;
|
||||||
pub mod slate;
|
pub mod slate;
|
||||||
|
|
||||||
use crate::consensus;
|
use crate::consensus;
|
||||||
|
|
|
@ -173,3 +173,58 @@ where
|
||||||
{
|
{
|
||||||
serializer.serialize_str(&to_hex(bytes.as_ref().to_vec()))
|
serializer.serialize_str(&to_hex(bytes.as_ref().to_vec()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test serialization methods of components that are being used
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::libtx::aggsig;
|
||||||
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
|
use crate::util::secp::{Message, Signature};
|
||||||
|
use crate::util::static_secp_instance;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
|
||||||
|
struct SerTest {
|
||||||
|
#[serde(with = "pubkey_serde")]
|
||||||
|
pub pub_key: PublicKey,
|
||||||
|
#[serde(with = "option_sig_serde")]
|
||||||
|
pub opt_sig: Option<Signature>,
|
||||||
|
#[serde(with = "sig_serde")]
|
||||||
|
pub sig: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerTest {
|
||||||
|
pub fn random() -> SerTest {
|
||||||
|
let static_secp = static_secp_instance();
|
||||||
|
let secp = static_secp.lock();
|
||||||
|
let sk = SecretKey::new(&secp, &mut thread_rng());
|
||||||
|
let mut msg = [0u8; 32];
|
||||||
|
thread_rng().fill(&mut msg);
|
||||||
|
let msg = Message::from_slice(&msg).unwrap();
|
||||||
|
let sig = aggsig::sign_single(&secp, &msg, &sk, None).unwrap();
|
||||||
|
SerTest {
|
||||||
|
pub_key: PublicKey::from_secret_key(&secp, &sk).unwrap(),
|
||||||
|
opt_sig: Some(sig.clone()),
|
||||||
|
sig: sig.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ser_secp_primitives() {
|
||||||
|
for _ in 0..10 {
|
||||||
|
let s = SerTest::random();
|
||||||
|
println!("Before Serialization: {:?}", s);
|
||||||
|
let serialized = serde_json::to_string_pretty(&s).unwrap();
|
||||||
|
println!("JSON: {}", serialized);
|
||||||
|
let deserialized: SerTest = serde_json::from_str(&serialized).unwrap();
|
||||||
|
println!("After Serialization: {:?}", deserialized);
|
||||||
|
println!();
|
||||||
|
assert_eq!(s, deserialized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ use crate::core::transaction::{kernel_features, kernel_sig_msg, Transaction, Wei
|
||||||
use crate::core::verifier_cache::LruVerifierCache;
|
use crate::core::verifier_cache::LruVerifierCache;
|
||||||
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
use crate::keychain::{BlindSum, BlindingFactor, Keychain};
|
||||||
use crate::libtx::error::{Error, ErrorKind};
|
use crate::libtx::error::{Error, ErrorKind};
|
||||||
use crate::libtx::{aggsig, build, tx_fee};
|
use crate::libtx::{aggsig, build, secp_ser, tx_fee};
|
||||||
use crate::util::secp;
|
use crate::util::secp;
|
||||||
use crate::util::secp::key::{PublicKey, SecretKey};
|
use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::secp::Signature;
|
use crate::util::secp::Signature;
|
||||||
|
@ -68,6 +68,33 @@ impl ParticipantData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Public message data (for serialising and storage)
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ParticipantMessageData {
|
||||||
|
/// id of the particpant in the tx
|
||||||
|
pub id: u64,
|
||||||
|
/// Public key
|
||||||
|
#[serde(with = "secp_ser::pubkey_serde")]
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
/// Message,
|
||||||
|
pub message: Option<String>,
|
||||||
|
/// Signature
|
||||||
|
#[serde(with = "secp_ser::option_sig_serde")]
|
||||||
|
pub message_sig: Option<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParticipantMessageData {
|
||||||
|
/// extract relevant message data from participant data
|
||||||
|
pub fn from_participant_data(p: &ParticipantData) -> ParticipantMessageData {
|
||||||
|
ParticipantMessageData {
|
||||||
|
id: p.id,
|
||||||
|
public_key: p.public_blind_excess,
|
||||||
|
message: p.message.clone(),
|
||||||
|
message_sig: p.message_sig.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A 'Slate' is passed around to all parties to build up all of the public
|
/// A 'Slate' is passed around to all parties to build up all of the public
|
||||||
/// transaction data needed to create a finalized transaction. Callers can pass
|
/// transaction data needed to create a finalized transaction. Callers can pass
|
||||||
/// the slate around by whatever means they choose, (but we can provide some
|
/// the slate around by whatever means they choose, (but we can provide some
|
||||||
|
@ -98,6 +125,13 @@ pub struct Slate {
|
||||||
pub version: Option<u64>,
|
pub version: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper just to facilitate serialization
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ParticipantMessages {
|
||||||
|
/// included messages
|
||||||
|
pub messages: Vec<ParticipantMessageData>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Slate {
|
impl Slate {
|
||||||
/// Create a new slate
|
/// Create a new slate
|
||||||
pub fn blank(num_participants: usize) -> Slate {
|
pub fn blank(num_participants: usize) -> Slate {
|
||||||
|
@ -279,10 +313,19 @@ impl Slate {
|
||||||
message: message,
|
message: message,
|
||||||
message_sig: message_sig,
|
message_sig: message_sig,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// helper to return all participant messages
|
||||||
|
pub fn participant_messages(&self) -> ParticipantMessages {
|
||||||
|
let mut ret = ParticipantMessages { messages: vec![] };
|
||||||
|
for ref m in self.participant_data.iter() {
|
||||||
|
ret.messages
|
||||||
|
.push(ParticipantMessageData::from_participant_data(m));
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
/// Somebody involved needs to generate an offset with their private key
|
/// Somebody involved needs to generate an offset with their private key
|
||||||
/// For now, we'll have the transaction initiator be responsible for it
|
/// For now, we'll have the transaction initiator be responsible for it
|
||||||
/// Return offset private key for the participant to use later in the
|
/// Return offset private key for the participant to use later in the
|
||||||
|
|
388
doc/intro_SE.md
Normal file
388
doc/intro_SE.md
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
# Introduktion till MimbleWimble och Grin
|
||||||
|
|
||||||
|
*Läs detta på andra språk: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md).*
|
||||||
|
|
||||||
|
MimbleWimble är ett blockkedjeformat och protokoll som erbjuder extremt bra
|
||||||
|
skalbarhet, integritet, och fungibilitet genom starka kryptografiska primitiver.
|
||||||
|
Den angriper brister som existerar i nästan alla nuvarande blockkedjeimplementationer.
|
||||||
|
|
||||||
|
Grin är ett mjukvaruprojekt med öppen källkod som implementerar en MimbleWimble-blockkedja
|
||||||
|
och fyller igen luckorna för att skapa en fullständig blockkedja och kryptovaluta.
|
||||||
|
|
||||||
|
Grin-projektets huvudsakliga mål och kännetecken är:
|
||||||
|
|
||||||
|
* Integritet som standard. Detta möjliggör fullkomlig fungibilitet utan att
|
||||||
|
förhindra förmågan att selektivt uppdaga information efter behov.
|
||||||
|
* Växer mestadels med antal användare och minimalt med antal transaktioner (< 100 bytes transaktionskärna),
|
||||||
|
vilket resulterar i stora utrymmesbesparingar i jämförelse med andra blockkedjor.
|
||||||
|
* Stark och bevisad kryptografi. MimbleWimble förlitar sig endast på kryptografi med
|
||||||
|
elliptiska kurvor (ECC) vilket har beprövats i decennier.
|
||||||
|
* Simplistik design som gör det enkelt att granska och underhålla på lång sikt.
|
||||||
|
* Gemenskapsdriven, uppmuntrar mining och decentralisering.
|
||||||
|
|
||||||
|
## Tungknytande för alla
|
||||||
|
|
||||||
|
Detta dokument är riktat mot läsare med en bra förståelse för blockkedjor och grundläggande kryptografi.
|
||||||
|
Med det i åtanke försöker vi förklara den tekniska uppbyggnaden av MimbleWimble och hur det appliceras i Grin.
|
||||||
|
Vi hoppas att detta dokument är föreståeligt för de flesta tekniskt inriktade läsare. Vårt mål är att
|
||||||
|
uppmuntra er att bli intresserade i Grin och bidra på något möjligt sätt.
|
||||||
|
|
||||||
|
För att uppnå detta mål kommer vi att introducera de huvudsakliga begrepp som krävs för en
|
||||||
|
bra förståelse för Grin som en MimbleWimble-implementation. Vi kommer att börja med en kort
|
||||||
|
beskrivning av några av elliptiska kurvornas relevanta egenskaper för att lägga grunden som Grin
|
||||||
|
är baserat på och därefter beskriva alla viktiga element i en MimbleWimble-blockkedjas
|
||||||
|
transaktioner och block.
|
||||||
|
|
||||||
|
### Småbitar av elliptiska kurvor
|
||||||
|
|
||||||
|
Vi börjar med en kort undervisning i kryptografi med elliptiska kurvor (ECC) där vi endast
|
||||||
|
går igenom de nödvändiga egenskaper för att förstå hur MimbleWimble fungerar utan att
|
||||||
|
gå djupt in på dess krångligheter. För läsare som vill fördjupa sig i detta finns andra
|
||||||
|
möjligheter att [lära sig mer](http://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/).
|
||||||
|
|
||||||
|
En elliptisk kurva för kryptografiska är ändamål är enkelt sagt en stor mängd av punkter
|
||||||
|
som vi kallar för _C_. Dessa punkter kan adderas, subtraheras, eller multipliceras med heltal (även kallat skalärer).
|
||||||
|
Given ett heltal _k_ kan vi beräkna `k*H` med skalärmultiplikation, vilket också är en punkt på kurvan _C_. Given ett annat
|
||||||
|
heltal _j_ kan vi också beräkna `(k+j)*H`, vilket är lika med `k*H + j*H`. Addition och skalärmultiplikation på elliptiska
|
||||||
|
kurvor behåller sina kommutativa och associativa egenskaper från vanlig addition och multiplikation:
|
||||||
|
|
||||||
|
(k+j)*H = k*H + j*H
|
||||||
|
|
||||||
|
Inom ECC, om vi väljer ett väldigt stort tal _k_ som privat nyckel så anses `k*H` vara dess publika nyckel. Även om
|
||||||
|
man vet värdet av den publika nyckeln `k*H`, är det nästintill omöjligt att härleda `k` (sagt med andra ord, medan
|
||||||
|
multiplikation är trivialt är "division" med kurvpunkter extremt svårt).
|
||||||
|
|
||||||
|
Den föregående formeln `(k+j)*H = k*H + j*H`, med _k_ och _j_ båda privata nycklar demonstrerar att en publik nyckel
|
||||||
|
erhållen av att ha adderat de två privata nycklarna är identisk med de två privata nycklarnas respektive
|
||||||
|
publika nycklar adderade (`k*H + j*H`). I Bitcoin-blockkedjan använder hierarkiska deterministiska plånböcker (HD wallets)
|
||||||
|
sig flitigt av denna princip. MimbleWimble och Grin-implementationer gör det också.
|
||||||
|
|
||||||
|
### Transaktioner med MimbleWimble
|
||||||
|
|
||||||
|
Transaktionernas struktur demonstrerar en av MimbleWimbles kritiska grundsatser:
|
||||||
|
starka garantier av integritet och konfidentialitet.
|
||||||
|
|
||||||
|
Valideringen av MimbleWimble-transaktioner använder sig av två grundläggande egenskaper:
|
||||||
|
|
||||||
|
* **Kontroll av nollsummor.** Summan av utmatningar minus inmatningar är alltid lika med noll, vilket bevisar—utan att
|
||||||
|
avslöja beloppen—att transaktionen inte skapade nya pengar.
|
||||||
|
* **Innehav av privata nycklar.** Som med de flesta andra kryptovalutar garanteras ägandet av transaktionsutmatningar
|
||||||
|
med innehavet av privata nycklar. Dock bevisas inte ägandet av dem genom en direkt signering av transaktionen.
|
||||||
|
|
||||||
|
De följande styckena angående saldo, ägande, växel, och bevis klarlägger hur de två grundläggande egenskaperna uppnås.
|
||||||
|
|
||||||
|
#### Saldo
|
||||||
|
|
||||||
|
Bygger vi på ECC-egenskaperna vi förklarade ovan kan vi beslöja beloppen i en transaktion.
|
||||||
|
|
||||||
|
Om _v_ är beloppet av en inmatning eller utmatning i en transaktion och _H_ en elliptisk kurva, kan vi enkelt bädda in
|
||||||
|
`v*H` i stället för _v_ i en transaktion. Detta fungerar eftersom vi fortfarande kan bekräfta att summan av utmatningarna är
|
||||||
|
lika med summan av inmatningarna i en transaktion med hjälp av ECC-operationer:
|
||||||
|
|
||||||
|
v1 + v2 = v3 => v1*H + v2*H = v3*H
|
||||||
|
|
||||||
|
Bekräftandet av denna egenskap på alla transaktioner låter protokollet bekräfta att en transaktion inte skapar pengar ur
|
||||||
|
tomma intet utan att veta vad beloppen är. Dock finns det ett begränsat antal av användbara belopp och man skulle kunna
|
||||||
|
prova varenda en för att gissa beloppet på din transaktion. Dessutom, om man känner till v1 (till exempel från en föregående
|
||||||
|
transaktion) och det resulterande `v1*H` avslöjar man alla utmatningar med beloppet v1 över hela blockkedjan. Av dessa
|
||||||
|
anledningar introducerar vi en till elliptisk kurva _G_ (i praktiken är _G_ endast en annan generatorpunkt på samma kurvgrupp
|
||||||
|
som _H_) och en privat nyckel _r_ som används som en *bländande faktor*.
|
||||||
|
|
||||||
|
Ett inmatnings- eller utmatningsbelopp i en transaktion kan uttryckas som:
|
||||||
|
|
||||||
|
r*G + v*H
|
||||||
|
|
||||||
|
Där:
|
||||||
|
|
||||||
|
* _r_ är en privat nyckel använd som en bländande faktor, _G_ är en elliptisk kurva, och deras
|
||||||
|
produkt `r*G` är den publika nyckeln för _r_ på _G_.
|
||||||
|
* _v_ är ett inmatnings- eller utmatningsbelopp och _H_ är en annan elliptisk kurva.
|
||||||
|
|
||||||
|
Varken _v_ eller _r_ kan härledas på grund av ECC:s grundläggande egenskaper. `r*G + v*H` kallas för
|
||||||
|
ett _Pedersen Commitment_.
|
||||||
|
|
||||||
|
Som ett exempel, låt oss anta att vi vill skapa en transaktion med två inmatningar och en utmatning.
|
||||||
|
Vi har (utan hänsyn till avgifter):
|
||||||
|
|
||||||
|
* vi1 och vi2 som inmatningsbelopp.
|
||||||
|
* vo3 som utmatningsbelopp.
|
||||||
|
|
||||||
|
Sådana att:
|
||||||
|
|
||||||
|
vi1 + vi2 = vo3
|
||||||
|
|
||||||
|
Vi genererar en privat nyckel som en bländande faktor för varje inmatningsbelopp och ersätter alla belopp med
|
||||||
|
deras respektive Pedersen Commitment och ekvationen blir därmed:
|
||||||
|
|
||||||
|
(ri1*G + vi1*H) + (ri2*G + vi2*H) = (ro3*G + vi3*H)
|
||||||
|
|
||||||
|
Vilket som följd kräver att:
|
||||||
|
|
||||||
|
ri1 + ri2 = ro3
|
||||||
|
|
||||||
|
Detta är MimbleWimbles första pelare: de beräkningar som är nödvändiga för att validera en transaktion
|
||||||
|
kan göras utan att veta några belopp.
|
||||||
|
|
||||||
|
Denna idé härstammar faktiskt från Greg Maxwells
|
||||||
|
[Confidential Transactions](https://elementsproject.org/features/confidential-transactions/investigation),
|
||||||
|
som i sin tur härstammar från ett förslag av Adam Back för homomorfiska belopp applicerade på Bitcoin.
|
||||||
|
|
||||||
|
#### Ägande
|
||||||
|
|
||||||
|
I föregående stycke introducerade vi en privat nyckel som en bländande faktor för att dölja transaktionens belopp.
|
||||||
|
MimbleWimbles andra insikt är att denna privata nyckel kan användas för att bevisa ägande av beloppet.
|
||||||
|
|
||||||
|
Alice skickar 3 mynt till dig och för att dölja beloppet väljer du 28 som din bländande faktor (notera att i praktiken
|
||||||
|
är den bländande faktorn ett extremt stort tal). Någonstans i blockkedjan dyker följande utmatning upp och ska endast
|
||||||
|
vara spenderbar av dig:
|
||||||
|
|
||||||
|
X = 28*G + 3*H
|
||||||
|
|
||||||
|
_X_ som är resultatet av additionen är synlig för alla. Beloppet 3 är endast känt av dig och Alice, och 28 är endast
|
||||||
|
känt av dig.
|
||||||
|
|
||||||
|
För att skicka dessa 3 mynt igen kräver protokollet att 28 ska vara känt. För att demonstrera hur detta fungerar, låt
|
||||||
|
oss säga att du vill skicka samma 3 mynt till Carol. Du behöver skapa en simpel transaktion sådan att:
|
||||||
|
|
||||||
|
Xi => Y
|
||||||
|
|
||||||
|
Där _Xi_ är en inmatning som spenderar din _X_-utmatning och Y är Carols utmatning. Det finns inget sätt att skapa
|
||||||
|
en sådan transaktion utan att känna till din privata nyckel 28. Om Carol ska balansera denna transaktion behöver hon
|
||||||
|
både känna till det skickade beloppet och din privata nyckel så att:
|
||||||
|
|
||||||
|
Y - Xi = (28*G + 3*H) - (28*G + 3*H) = 0*G + 0*H
|
||||||
|
|
||||||
|
Genom att kontrollera att allt har nollställts kan vi återigen försäkra oss om att inga nya pengar har skapats.
|
||||||
|
|
||||||
|
Vänta! Stopp! Nu känner du till den privata nyckeln i Carols utmatning (vilket i detta fall måste vara samma som ditt
|
||||||
|
för att balansera in- och utmatningarna) så du skulle kunna stjäla tillbaka pengarna från Carol!
|
||||||
|
|
||||||
|
För att lösa detta problem använder Carol en privat nyckel som hon väljer själv. Låt oss säga att hon väljer 113.
|
||||||
|
Det som hamnar i blockkedjan är:
|
||||||
|
|
||||||
|
Y - Xi = (113*G + 3*H) - (28*G + 3*H) = 85*G + 0*H
|
||||||
|
|
||||||
|
Nu summeras transaktionen inte längre till noll och vi har ett _överskottsbelopp_ på _G_ (85), vilket är resultatet
|
||||||
|
av summeringen av alla bländande faktorer. Men eftersom `85*G` är en giltig publik nyckel på elliptiska kurvan _G_ vet vi
|
||||||
|
att in- och utmatningarna har subtraheras till noll och transaktionen är därmed giltig.
|
||||||
|
|
||||||
|
Så allt protokollet behöver göra är att kontrollera att (`Y - Xi`) är en giltig publik nyckel på _G_ och att de två parter
|
||||||
|
som utför transaktionen tillsammans kan producera den privata nyckeln (85 i exemplet ovan). Det enklaste sättet att göra
|
||||||
|
det är att kräva en signatur med överskottsbeloppet (85), vilket bekräftar att:
|
||||||
|
|
||||||
|
* De parter som utför transaktionen känner till den privata nyckeln, och
|
||||||
|
* Summan av utmatningarna minus inmatningarna i transaktionen är noll (eftersom överskottsbeloppet måste vara en publik nyckel).
|
||||||
|
|
||||||
|
Denna signatur som tillsammans med lite annan information (som exempelvis mining-avgifter) bifogas till transaktionen kallas
|
||||||
|
för _transaktionskärna_ och kontrolleras av alla validerare.
|
||||||
|
|
||||||
|
#### Några finare punkter
|
||||||
|
|
||||||
|
Detta stycke detaljerar byggandet av transaktioner genom att diskutera hur växel införs och kravet för "range proofs"
|
||||||
|
så att alla belopp är bevisade att vara icke-negativa. Inget av detta är absolut nödvändigt för att förstå MimbleWimble
|
||||||
|
och Grin, så om du har bråttom känn dig fri att hoppa direkt till [Sammanställningen av allt](#sammanställningen-av-allt).
|
||||||
|
|
||||||
|
#### Växel
|
||||||
|
|
||||||
|
Låt oss säga att du endast vill skicka 2 mynt till Carol av de 3 mynt du mottog från Alice. För att göra detta behöver du
|
||||||
|
skicka det återstående myntet tillbaka till dig själv som växel. Du genererar en annan privat nyckel (t ex 12) som en
|
||||||
|
bländande faktor för att skydda ditt växel-utmatningsbelopp. Carol använder sin egen privata nyckel som tidigare.
|
||||||
|
|
||||||
|
Växel-utmatning: 12*G + 1*H
|
||||||
|
Carols utmatning: 113*G + 2*H
|
||||||
|
|
||||||
|
Det som hamnar i blockkedjan är något väldigt likt det vi hade tidigare, och signaturen är återigen skapat med
|
||||||
|
överskottsbeloppet, 97 i detta exempel.
|
||||||
|
|
||||||
|
(12*G + 1*H) + (113*G + 2*H) - (28*G + 3*H) = 97*G + 0*H
|
||||||
|
|
||||||
|
#### Range Proofs
|
||||||
|
|
||||||
|
I alla beräkningar ovan förlitar vi oss på att alla belopp är positiva. Introduktionen av negativa belopp skulle vara
|
||||||
|
extremt problematiskt då man skulle kunna skapa nya pengar i varje transaktion.
|
||||||
|
|
||||||
|
Till exempel skulle man kunna skapa en transaktion med inmatningen 2 och utmatningar 5 och -3 och fortfarande
|
||||||
|
ha en balanserad transaktion. Detta kan inte upptäcklas enkelt eftersom punkten `x*H` ser ut som vilken annan punkt
|
||||||
|
som helst på kurvan även om _x_ är negativt.
|
||||||
|
|
||||||
|
För att lösa detta problem använder MimbleWimble sig av ett kryptografiskt koncept som kallas "range proofs" (som också härstammar
|
||||||
|
från Confidential Transactions): ett bevis på att ett tal befinner sig inom ett visst intervall utan att avsölja talet.
|
||||||
|
Vi kommer inte att förklara range proofs; du behöver endast veta att för varje `r*G + v*H` kan vi skapa ett bevis som visar
|
||||||
|
att _v_ är större än noll och inte orsakar overflow.
|
||||||
|
|
||||||
|
Det är även viktigt att notera att både värdet 113 och värdet 28 måste vara kända för att kunna skapa ett giltigt range proof.
|
||||||
|
Anledningen till detta och en mer utförlig beskrivning av range proofs är förklarat i
|
||||||
|
[range proof-pappret](https://eprint.iacr.org/2017/1066.pdf).
|
||||||
|
|
||||||
|
#### Sammanställningen av allt
|
||||||
|
|
||||||
|
En MimbleWimble-transaktion inkluderar följande:
|
||||||
|
|
||||||
|
* En mängd inmatningar som refererar till och spenderar en mängd föregående utmatningar.
|
||||||
|
* En mängd nya utmatningar som inkluderar:
|
||||||
|
* Ett belopp och en bländande faktor (vilket bara är en ny privat nyckel) multiplicerade på en kurva och adderade
|
||||||
|
till att bli `r*G + v*H`.
|
||||||
|
* Ett range proof som visar att v är icke-negativt.
|
||||||
|
* En tydlig transaktionsavgift i klartext.
|
||||||
|
* En signatur vars privata nyckel beräknas genom att ta överskottsbeloppet (summan av alla utmatningar och
|
||||||
|
avgiften minus inmatningarna).
|
||||||
|
|
||||||
|
### Block och kedjetillstånd
|
||||||
|
|
||||||
|
Vi förklarade ovan hur MimbleWimble-transaktioner kan erbjuda starka anonymitetsgarantier samtidigt som de
|
||||||
|
upprätthåller egenskaperna för en giltig blockkedja, d.v.s en transaktion skapar inte pengar och ägandebevis är
|
||||||
|
fastställt med privata nycklar.
|
||||||
|
|
||||||
|
MimbleWimble-blockformatet bygger på detta genom att introducera ett till koncept: _genomskärning_. Med detta
|
||||||
|
får en MimbleWimble-kedja:
|
||||||
|
|
||||||
|
* Extremt bra skalbarhet då den stora majoriteten av transaktionsinformation kan elimineras på lång sikt utan att
|
||||||
|
kompromissa säkerhet.
|
||||||
|
* Ytterligare anonymitet genom att blanda och ta bort transaktionsinformation.
|
||||||
|
* Förmågan att effektivt synkronisera sig med resten av nätverket för nya noder.
|
||||||
|
|
||||||
|
#### Transaktionsaggregation
|
||||||
|
|
||||||
|
Kom igåg att en transaktion består av följande:
|
||||||
|
|
||||||
|
* En mängd inmatningar som refererar till och spenderar en mängd föregående utmatningar
|
||||||
|
* En mängd nya utmatningar (Pedersen commitments)
|
||||||
|
* En transaktionskärna som består av:
|
||||||
|
* överskottsbelopp
|
||||||
|
* transaktionssignatur
|
||||||
|
|
||||||
|
En transaktion signeras och signaturen inkluderas i en transaktionskärna. Signaturen genereras genom att använda
|
||||||
|
överskottsbeloppet som en publik nyckel för att bevisa att beloppen summeras till 0:
|
||||||
|
|
||||||
|
(42*G + 1*H) + (99*G + 2*H) - (113*G + 3*H) = 28*G + 0*H
|
||||||
|
|
||||||
|
Den publika nyckeln i detta exempel är `28*G`.
|
||||||
|
|
||||||
|
Vi kan säga att följande är sant för alla giltiga transaktioner (vi ignorerar avgifter för enkelhetens skull):
|
||||||
|
|
||||||
|
summa(utmatningar) - summa(inmatningar) = överskottsbelopp
|
||||||
|
|
||||||
|
Detsamma gäller för blocken själva när vi inser att ett block helt enkelt är en mängd aggregerade inmatningar, utmatningar, och
|
||||||
|
transaktionskärnor. Vi kan summera transaktionsutmatningarna, subtrahera summan av transaktionsinmatningarna, och jämföra
|
||||||
|
det resulterande Pedersen commitment med summan av överskottsbeloppen:
|
||||||
|
|
||||||
|
summa(utmatningar) - summa(inmatningar) = summa(överskottsbelopp)
|
||||||
|
|
||||||
|
|
||||||
|
Något förenklat, (återigen ignorerar vi transaktionsavgifter) kan vi säga att MimbleWimble-block kan betraktas precis som
|
||||||
|
MimbleWimble-transaktioner.
|
||||||
|
|
||||||
|
##### Kärn-offset
|
||||||
|
|
||||||
|
Det finns ett subtilt problem med MimbleWimble-block och transaktioner som beskrivet ovan. Det är möjligt (och i vissa fall
|
||||||
|
trivialt) att rekonstruera de konstituerande transaktionerna i ett block. Detta är naturligtvis dåligt för integriteten.
|
||||||
|
Detta är "delmängdsproblemet": given en mängd inmatningar, utmatningar, och transaktionskärnor kommer någon delmängd av detta
|
||||||
|
kunna kombineras för att rekonstruera en giltig transaktion.
|
||||||
|
|
||||||
|
Till exempel, vi har följande två transaktioner:
|
||||||
|
|
||||||
|
(inmatning1, inmatning2) -> (utmatning1), (kärna1)
|
||||||
|
(inmatning3) -> (utmatning2), (kärna2)
|
||||||
|
|
||||||
|
Vi kan aggregera dem till följande block:
|
||||||
|
|
||||||
|
(inmatning1, inmatning2, inmatning3) -> (utmatning1, utmatning2), (kärna1, kärna2)
|
||||||
|
|
||||||
|
Det är trivialt att testa alla möjliga kombinationer och återskapa en av transaktionerna (där summan lyckas bli noll).
|
||||||
|
|
||||||
|
(inmatning1, inmatning2) -> (utmatning1), (kärna1)
|
||||||
|
|
||||||
|
Vi vet också att allt som kvarstår kan användas för att rekonstruera den andra giltiga transaktionen:
|
||||||
|
|
||||||
|
(inmatning3) -> (utmatning2), (kärna2)
|
||||||
|
|
||||||
|
För att mildra detta inkluderar vi ett _kärn-offset_ med varje överskottsbelopp. Detta är en bländande faktor som måste
|
||||||
|
tilläggas överskottsbeloppet för att verifiera att det summeras till noll:
|
||||||
|
|
||||||
|
summa(utmatningar) - summa(inmatningar) = överskottsbelopp + kärn-offset
|
||||||
|
|
||||||
|
Vi "separerar" nyckeln `k` till `k1 + k2` under transaktionsbyggandet. För ett överskottsbelopp `(k1+k2)*G` publicerar vi
|
||||||
|
`k1*G` (överskottet) och `k2` (offset) och signerar transaktionen med `k1*G` som tidigare. Under block-konstruktionen
|
||||||
|
kan vi enkelt summera alla `k2`-offset för att generera ett aggregat-offset för alla transaktioner i blocket. `k2`-offsetet
|
||||||
|
för en individuell transaktion är omöjlig att få fram.
|
||||||
|
|
||||||
|
#### Genomskärning
|
||||||
|
|
||||||
|
Blocks låter miners sätta ihop flera transaktioner till en enstaka mängd som läggs till på kedjan. I följande
|
||||||
|
block-representationer som innerhåller tre transaktioner visar vi endast in- och utmatningarna. Inmatningar refererar till
|
||||||
|
föregående utmatningar som de spenderar. Föregående utmatningar markeras med _x_.
|
||||||
|
|
||||||
|
I1(x1) --- O1
|
||||||
|
|- O2
|
||||||
|
|
||||||
|
I2(x2) --- O3
|
||||||
|
I3(O2) -|
|
||||||
|
|
||||||
|
I4(O3) --- O4
|
||||||
|
|- O5
|
||||||
|
|
||||||
|
Vi lägger märke till följande två egenskaper:
|
||||||
|
|
||||||
|
* Inom detta block är vissa utmatningar spenderade direkt av inkluderade inmatningar (I3 spenderar O2, och I4 spenderar O3).
|
||||||
|
* Transaktionernas struktur spelar faktiskt ingen roll. Eftersom alla transaktioner individuellt summeras till noll
|
||||||
|
måste summan av alla transaktionsinmatningar och utmatningar summera till noll.
|
||||||
|
|
||||||
|
Liknande en transaktion, allt som behöver kontrolleras i ett block är att ägandebevis (vilket kommer från transaktionskärnorna)
|
||||||
|
och att blocket i helhet inte skapade pengar ur tomma intet. Således kan matchande inmatningar och utmatningar elimineras, då
|
||||||
|
deras sammansatta påverkan är noll. Detta leder till följande, mycket mer kompakta block:
|
||||||
|
|
||||||
|
I1(x1) | O1
|
||||||
|
I2(x2) | O4
|
||||||
|
| O5
|
||||||
|
|
||||||
|
Notera att all transaktionsstruktur har eliminerats och att ordningen av in- och utmatningar inte längre spelar någon roll.
|
||||||
|
Summan av alla in- och utmatningar garanteras fortfarande vara noll.
|
||||||
|
|
||||||
|
Ett block består av:
|
||||||
|
|
||||||
|
* En block-header.
|
||||||
|
* En lista av alla inmatningar som kvarstår efter genomskärning.
|
||||||
|
* En lista av alla utmatningar som kvarstår efter genomskärning.
|
||||||
|
* Ett enstaka kärn-offset som skyddar hela blocket.
|
||||||
|
* Transaktionskärnor för varje transaktion som innehåller:
|
||||||
|
* Publika nyckeln `r*G` erhållen genom summation av alla commitments.
|
||||||
|
* Signaturerna genererade genom överskottsbeloppet.
|
||||||
|
* Mining-avgiften
|
||||||
|
|
||||||
|
Med denna struktur erbjuder ett MimbleWimble-block extremt bra integritetsgarantier:
|
||||||
|
|
||||||
|
* Mellanliggande transaktioner är endast representerade av sina transaktionskärnor.
|
||||||
|
* Alla utmatningar ser likadana ut: väldigt stora tal som inte går att skilja åt på något meningsfullt sätt.
|
||||||
|
Om någon vill exkludera en specifik utmatning är de tvungna att exkludera alla.
|
||||||
|
* All transaktionsstruktur har borttagits vilket gör det omöjligt att se vilka in- och utmatningar som passar ihop.
|
||||||
|
|
||||||
|
Men ändå kan allting valideras!
|
||||||
|
|
||||||
|
#### Genomskärning hela vägen
|
||||||
|
|
||||||
|
Vi går tillbaka till blocket i föregående exempel. Utmatningarna x1 och x2 som spenderades av I1 och I2 måste ha
|
||||||
|
dykt upp tidigare i blockkedjan. Efter att detta block adderas till blockkedjan kan de utmatningarna tillsammans med
|
||||||
|
I1 och I2 alla tas bort från blockkedjan eftersom de nu är mellanliggande transaktioner.
|
||||||
|
|
||||||
|
Vi slutleder att kedjetillståndet kan (bortsett från block-headers) vid varje tidspunkt sammanfattas med endast dessa tre ting:
|
||||||
|
|
||||||
|
1. Den totala mängden mynt skapade genom mining.
|
||||||
|
2. Den kompletta mängden av oförbrukade utmatningar (UTXO).
|
||||||
|
3. Transaktionskärnorna för varje transaktion.
|
||||||
|
|
||||||
|
Det första kan härledas genom att endast observera block-höjden.
|
||||||
|
|
||||||
|
Både mängden av oförbrukade utmatningar och transaktionskärnorna är extremt kompakta. Detta har två följder:
|
||||||
|
|
||||||
|
* En nod i en MimbleWimble-blockkedja får en väldigt liten kedja att behöva ta vara på.
|
||||||
|
* När en ny nod ansluter sig till närverket krävs det väldigt lite information för att den ska bygga kedjan.
|
||||||
|
|
||||||
|
Dessutom kan man inte manipulera mängden av de oförbrukade utmatningarna. Tar man bort ett element ändras summan av
|
||||||
|
de bländande faktorerna och in- och utmatningarna matchar inte längre varandra.
|
||||||
|
|
||||||
|
### Slutsats
|
||||||
|
|
||||||
|
I detta dokument gick vi igenom de grundläggande principerna för en MimbleWimble-blockkedja. Genom att använda egenskaperna
|
||||||
|
för addition i kryptografi med elliptiska kurvor kan vi skapa fullständigt förmörkade transaktioner som ändå kan valideras.
|
||||||
|
Genom att generalisera dessa egenskaper till block kan vi eliminera en stor mängd blockkedjeinformation vilket medför
|
||||||
|
väldigt bra skalbarhet.
|
|
@ -253,6 +253,16 @@ mod wallet_tests {
|
||||||
let mut bh = 10u64;
|
let mut bh = 10u64;
|
||||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
|
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
|
||||||
|
|
||||||
|
let very_long_message = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||||
|
This part should all be truncated";
|
||||||
|
|
||||||
// Update info and check
|
// Update info and check
|
||||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
@ -273,7 +283,7 @@ mod wallet_tests {
|
||||||
"-d",
|
"-d",
|
||||||
&file_name,
|
&file_name,
|
||||||
"-g",
|
"-g",
|
||||||
"Love, Yeast",
|
very_long_message,
|
||||||
"10",
|
"10",
|
||||||
];
|
];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
@ -491,6 +501,12 @@ mod wallet_tests {
|
||||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "txs"];
|
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "txs"];
|
||||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
|
// message output (mostly spit out for a visual in test logs)
|
||||||
|
let arg_vec = vec![
|
||||||
|
"grin", "wallet", "-p", "password", "-a", "mining", "txs", "-i", "10",
|
||||||
|
];
|
||||||
|
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||||
|
|
||||||
// txs and outputs (mostly spit out for a visual in test logs)
|
// txs and outputs (mostly spit out for a visual in test logs)
|
||||||
let arg_vec = vec![
|
let arg_vec = vec![
|
||||||
"grin", "wallet", "-p", "password", "-a", "mining", "outputs",
|
"grin", "wallet", "-p", "password", "-a", "mining", "outputs",
|
||||||
|
|
|
@ -396,15 +396,19 @@ pub fn txs(
|
||||||
&g_args.account,
|
&g_args.account,
|
||||||
height,
|
height,
|
||||||
validated,
|
validated,
|
||||||
txs,
|
&txs,
|
||||||
include_status,
|
include_status,
|
||||||
dark_scheme,
|
dark_scheme,
|
||||||
)?;
|
)?;
|
||||||
// if given a particular transaction id, also get and display associated
|
// if given a particular transaction id, also get and display associated
|
||||||
// inputs/outputs
|
// inputs/outputs and messages
|
||||||
if args.id.is_some() {
|
if args.id.is_some() {
|
||||||
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
let (_, outputs) = api.retrieve_outputs(true, false, args.id)?;
|
||||||
display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?;
|
display::outputs(&g_args.account, height, validated, outputs, dark_scheme)?;
|
||||||
|
// should only be one here, but just in case
|
||||||
|
for tx in txs {
|
||||||
|
display::tx_messages(&tx, dark_scheme)?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -123,7 +123,7 @@ pub fn txs(
|
||||||
account: &str,
|
account: &str,
|
||||||
cur_height: u64,
|
cur_height: u64,
|
||||||
validated: bool,
|
validated: bool,
|
||||||
txs: Vec<TxLogEntry>,
|
txs: &Vec<TxLogEntry>,
|
||||||
include_status: bool,
|
include_status: bool,
|
||||||
dark_background_color_scheme: bool,
|
dark_background_color_scheme: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
@ -357,3 +357,77 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
|
||||||
table.printstd();
|
table.printstd();
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display transaction log messages
|
||||||
|
pub fn tx_messages(tx: &TxLogEntry, dark_background_color_scheme: bool) -> Result<(), Error> {
|
||||||
|
let title = format!("Transaction Messages - Transaction '{}'", tx.id,);
|
||||||
|
println!();
|
||||||
|
let mut t = term::stdout().unwrap();
|
||||||
|
t.fg(term::color::MAGENTA).unwrap();
|
||||||
|
writeln!(t, "{}", title).unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
|
||||||
|
let msgs = match tx.messages.clone() {
|
||||||
|
None => {
|
||||||
|
writeln!(t, "{}", "None").unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Some(m) => m.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if msgs.messages.is_empty() {
|
||||||
|
writeln!(t, "{}", "None").unwrap();
|
||||||
|
t.reset().unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = table!();
|
||||||
|
|
||||||
|
table.set_titles(row![
|
||||||
|
bMG->"Participant Id",
|
||||||
|
bMG->"Message",
|
||||||
|
bMG->"Public Key",
|
||||||
|
bMG->"Signature",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let secp = util::static_secp_instance();
|
||||||
|
let secp_lock = secp.lock();
|
||||||
|
|
||||||
|
for m in msgs.messages {
|
||||||
|
let id = format!("{}", m.id);
|
||||||
|
let public_key = format!(
|
||||||
|
"{}",
|
||||||
|
util::to_hex(m.public_key.serialize_vec(&secp_lock, true).to_vec())
|
||||||
|
);
|
||||||
|
let message = match m.message {
|
||||||
|
Some(m) => format!("{}", m),
|
||||||
|
None => "None".to_owned(),
|
||||||
|
};
|
||||||
|
let message_sig = match m.message_sig {
|
||||||
|
Some(s) => format!("{}", util::to_hex(s.serialize_der(&secp_lock))),
|
||||||
|
None => "None".to_owned(),
|
||||||
|
};
|
||||||
|
if dark_background_color_scheme {
|
||||||
|
table.add_row(row![
|
||||||
|
bFC->id,
|
||||||
|
bFC->message,
|
||||||
|
bFC->public_key,
|
||||||
|
bFB->message_sig,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
table.add_row(row![
|
||||||
|
bFD->id,
|
||||||
|
bFb->message,
|
||||||
|
bFD->public_key,
|
||||||
|
bFB->message_sig,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
|
||||||
|
table.printstd();
|
||||||
|
println!();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ use crate::libwallet::{Error, ErrorKind};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::util::secp::{pedersen, ContextFlag, Secp256k1};
|
use crate::util::secp::{pedersen, ContextFlag, Secp256k1};
|
||||||
|
|
||||||
|
const USER_MESSAGE_MAX_LEN: usize = 256;
|
||||||
|
|
||||||
/// Functions intended for use by the owner (e.g. master seed holder) of the wallet.
|
/// Functions intended for use by the owner (e.g. master seed holder) of the wallet.
|
||||||
pub struct APIOwner<W: ?Sized, C, K>
|
pub struct APIOwner<W: ?Sized, C, K>
|
||||||
where
|
where
|
||||||
|
@ -551,7 +553,8 @@ where
|
||||||
/// ParticipantData within the slate. This message will include a signature created with the
|
/// ParticipantData within the slate. This message will include a signature created with the
|
||||||
/// sender's private keys, and will be publically verifiable. Note this message is for
|
/// sender's private keys, and will be publically verifiable. Note this message is for
|
||||||
/// the convenience of the participants during the exchange; it is not included in the final
|
/// the convenience of the participants during the exchange; it is not included in the final
|
||||||
/// transaction sent to the chain. Validation of this message is optional.
|
/// transaction sent to the chain. The message will be truncated to 256 characters.
|
||||||
|
/// Validation of this message is optional.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * a result containing:
|
/// * a result containing:
|
||||||
|
@ -640,6 +643,14 @@ where
|
||||||
None => w.parent_key_id(),
|
None => w.parent_key_id(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
Some(mut m) => {
|
||||||
|
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let (slate, context, lock_fn) = tx::create_send_tx(
|
let (slate, context, lock_fn) = tx::create_send_tx(
|
||||||
&mut *w,
|
&mut *w,
|
||||||
amount,
|
amount,
|
||||||
|
@ -685,12 +696,12 @@ where
|
||||||
let context = w.get_private_context(slate.id.as_bytes())?;
|
let context = w.get_private_context(slate.id.as_bytes())?;
|
||||||
tx::complete_tx(&mut *w, slate, &context)?;
|
tx::complete_tx(&mut *w, slate, &context)?;
|
||||||
tx::update_stored_tx(&mut *w, slate)?;
|
tx::update_stored_tx(&mut *w, slate)?;
|
||||||
|
tx::update_message(&mut *w, slate)?;
|
||||||
{
|
{
|
||||||
let mut batch = w.batch()?;
|
let mut batch = w.batch()?;
|
||||||
batch.delete_private_context(slate.id.as_bytes())?;
|
batch.delete_private_context(slate.id.as_bytes())?;
|
||||||
batch.commit()?;
|
batch.commit()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
w.close()?;
|
w.close()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -873,6 +884,15 @@ where
|
||||||
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
|
return Err(ErrorKind::TransactionAlreadyReceived(slate.id.to_string()).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let message = match message {
|
||||||
|
Some(mut m) => {
|
||||||
|
m.truncate(USER_MESSAGE_MAX_LEN);
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
let res = tx::receive_tx(&mut *w, slate, &parent_key_id, message);
|
let res = tx::receive_tx(&mut *w, slate, &parent_key_id, message);
|
||||||
w.close()?;
|
w.close()?;
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ pub enum ErrorKind {
|
||||||
CallbackImpl(&'static str),
|
CallbackImpl(&'static str),
|
||||||
|
|
||||||
/// Wallet backend error
|
/// Wallet backend error
|
||||||
#[fail(display = "Wallet store error")]
|
#[fail(display = "Wallet store error: {}", _0)]
|
||||||
Backend(String),
|
Backend(String),
|
||||||
|
|
||||||
/// Callback implementation error conversion
|
/// Callback implementation error conversion
|
||||||
|
|
|
@ -100,6 +100,7 @@ where
|
||||||
|
|
||||||
let lock_inputs = context.get_inputs().clone();
|
let lock_inputs = context.get_inputs().clone();
|
||||||
let _lock_outputs = context.get_outputs().clone();
|
let _lock_outputs = context.get_outputs().clone();
|
||||||
|
let messages = Some(slate.participant_messages());
|
||||||
|
|
||||||
// Return a closure to acquire wallet lock and lock the coins being spent
|
// Return a closure to acquire wallet lock and lock the coins being spent
|
||||||
// so we avoid accidental double spend attempt.
|
// so we avoid accidental double spend attempt.
|
||||||
|
@ -122,6 +123,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
t.amount_debited = amount_debited;
|
t.amount_debited = amount_debited;
|
||||||
|
t.messages = messages;
|
||||||
|
|
||||||
// write the output representing our change
|
// write the output representing our change
|
||||||
for (change_amount, id, _) in &change_amounts_derivations {
|
for (change_amount, id, _) in &change_amounts_derivations {
|
||||||
|
@ -195,6 +197,7 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
context.add_output(&key_id, &None);
|
context.add_output(&key_id, &None);
|
||||||
|
let messages = Some(slate.participant_messages());
|
||||||
|
|
||||||
// Create closure that adds the output to recipient's wallet
|
// Create closure that adds the output to recipient's wallet
|
||||||
// (up to the caller to decide when to do)
|
// (up to the caller to decide when to do)
|
||||||
|
@ -206,6 +209,7 @@ where
|
||||||
t.tx_slate_id = Some(slate_id);
|
t.tx_slate_id = Some(slate_id);
|
||||||
t.amount_credited = amount;
|
t.amount_credited = amount;
|
||||||
t.num_outputs = 1;
|
t.num_outputs = 1;
|
||||||
|
t.messages = messages;
|
||||||
batch.save(OutputData {
|
batch.save(OutputData {
|
||||||
root_key_id: parent_key_id.clone(),
|
root_key_id: parent_key_id.clone(),
|
||||||
key_id: key_id_inner.clone(),
|
key_id: key_id_inner.clone(),
|
||||||
|
|
|
@ -39,7 +39,6 @@ where
|
||||||
// create an output using the amount in the slate
|
// create an output using the amount in the slate
|
||||||
let (_, mut context, receiver_create_fn) =
|
let (_, mut context, receiver_create_fn) =
|
||||||
selection::build_recipient_output_with_slate(wallet, slate, parent_key_id.clone())?;
|
selection::build_recipient_output_with_slate(wallet, slate, parent_key_id.clone())?;
|
||||||
|
|
||||||
// fill public keys
|
// fill public keys
|
||||||
let _ = slate.fill_round_1(
|
let _ = slate.fill_round_1(
|
||||||
wallet.keychain(),
|
wallet.keychain(),
|
||||||
|
@ -55,6 +54,8 @@ where
|
||||||
// Save output in wallet
|
// Save output in wallet
|
||||||
let _ = receiver_create_fn(wallet);
|
let _ = receiver_create_fn(wallet);
|
||||||
|
|
||||||
|
update_message(wallet, slate)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +225,26 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the transaction participant messages
|
||||||
|
pub fn update_message<T: ?Sized, C, K>(wallet: &mut T, slate: &Slate) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: WalletBackend<C, K>,
|
||||||
|
C: NodeClient,
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
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()?;
|
||||||
|
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(())
|
||||||
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::core::libtx::build;
|
use crate::core::libtx::build;
|
||||||
|
|
|
@ -473,7 +473,10 @@ where
|
||||||
let parent_key_id = wallet.parent_key_id();
|
let parent_key_id = wallet.parent_key_id();
|
||||||
|
|
||||||
let key_id = match key_id {
|
let key_id = match key_id {
|
||||||
Some(key_id) => keys::retrieve_existing_key(wallet, key_id, None)?.0,
|
Some(key_id) => match keys::retrieve_existing_key(wallet, key_id, None) {
|
||||||
|
Ok(k) => k.0,
|
||||||
|
Err(_) => keys::next_available_key(wallet)?,
|
||||||
|
},
|
||||||
None => keys::next_available_key(wallet)?,
|
None => keys::next_available_key(wallet)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ use crate::util::secp::key::{PublicKey, SecretKey};
|
||||||
use crate::util::secp::{self, pedersen, Secp256k1};
|
use crate::util::secp::{self, pedersen, Secp256k1};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
|
use grin_core::libtx::slate::ParticipantMessages;
|
||||||
use serde;
|
use serde;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -610,6 +611,8 @@ pub struct TxLogEntry {
|
||||||
pub amount_debited: u64,
|
pub amount_debited: u64,
|
||||||
/// Fee
|
/// Fee
|
||||||
pub fee: Option<u64>,
|
pub fee: Option<u64>,
|
||||||
|
/// Message data, stored as json
|
||||||
|
pub messages: Option<ParticipantMessages>,
|
||||||
/// Location of the store transaction, (reference or resending)
|
/// Location of the store transaction, (reference or resending)
|
||||||
pub stored_tx: Option<String>,
|
pub stored_tx: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -643,6 +646,7 @@ impl TxLogEntry {
|
||||||
num_inputs: 0,
|
num_inputs: 0,
|
||||||
num_outputs: 0,
|
num_outputs: 0,
|
||||||
fee: None,
|
fee: None,
|
||||||
|
messages: None,
|
||||||
stored_tx: None,
|
stored_tx: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ use std::fs;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
fn clean_output_dir(test_dir: &str) {
|
fn clean_output_dir(test_dir: &str) {
|
||||||
let _ = fs::remove_dir_all(test_dir);
|
let _ = fs::remove_dir_all(test_dir);
|
||||||
}
|
}
|
||||||
|
@ -137,7 +139,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
|
|
||||||
// wallet 2 receives file, completes, sends file back
|
// wallet 2 receives file, completes, sends file back
|
||||||
wallet::controller::foreign_single_use(wallet2.clone(), |api| {
|
wallet::controller::foreign_single_use(wallet2.clone(), |api| {
|
||||||
api.receive_tx(&mut slate, None, Some(sender2_message))?;
|
api.receive_tx(&mut slate, None, Some(sender2_message.clone()))?;
|
||||||
adapter.send_tx_async(&receive_file, &mut slate)?;
|
adapter.send_tx_async(&receive_file, &mut slate)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
@ -174,6 +176,36 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Check messages, all participants should have both
|
||||||
|
wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||||
|
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[0].message,
|
||||||
|
Some(message.to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[1].message,
|
||||||
|
Some(sender2_message.to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
let msg_json = serde_json::to_string_pretty(&tx[0].clone().messages.unwrap()).unwrap();
|
||||||
|
println!("{}", msg_json);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
wallet::controller::owner_single_use(wallet2.clone(), |api| {
|
||||||
|
let (_, tx) = api.retrieve_txs(true, None, Some(slate.id))?;
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[0].message,
|
||||||
|
Some(message.to_owned())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tx[0].clone().messages.unwrap().messages[1].message,
|
||||||
|
Some(sender2_message)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
// let logging finish
|
// let logging finish
|
||||||
thread::sleep(Duration::from_millis(200));
|
thread::sleep(Duration::from_millis(200));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue