Merge remote-tracking branch 'upstream/master' into feature/slate-version

This commit is contained in:
Yoni 2019-01-31 15:52:39 +02:00
commit e505726d73
17 changed files with 684 additions and 19 deletions

1
Cargo.lock generated
View file

@ -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)",
] ]

View file

@ -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"

View file

@ -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

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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
View 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__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__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.

View file

@ -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",

View file

@ -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(())
})?; })?;

View file

@ -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(())
}

View file

@ -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()?;

View file

@ -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

View file

@ -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(),

View file

@ -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;

View file

@ -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)?,
}; };

View file

@ -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,
} }
} }

View file

@ -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(())