En utvecklarguide till zkGalaxy

En utvecklarguide till zkGalaxy

Källnod: 1946171

Beskrivning

Vitaliks avvägningar för zkEVMs mellan prestanda och kompatibilitet

Detta är en extremt användbar heuristik för att skilja på metoder för att stödja en zkEVM. Men zkEVM är en delmängd av alla möjliga sätt att bygga noll kunskapsapplikationer. För en programmerare som vill utnyttja de unika egenskaperna hos zk-beräkning, nämligen kortfattadhet, noll kunskap och korrekthet, en zkEVM kanske inte är det bästa valet. Genom att lägga ut hela uppsättningen av utvecklarverktyg hoppas jag kunna ge en guide som hjälper dig i beslutsprocessen kring rätt zk-stack för din applikation.

Under de senaste åren eller två har det skett enorma framsteg inom zk-verktyg. De närmar sig en punkt där vanliga mjukvaruutvecklare kan utnyttja de kraftfulla egenskaperna hos zk utan en djup förståelse för den skrämmande underliggande matematiken och tekniken. Å andra sidan har det skett en spridning av verktyg för avancerade användare som ger zk-experter extremt fin kontroll över zk-stacken.

Kraften i att abstrahera komplexitet

Modern programvara är byggd på otaliga lager av abstraktion för att maximera specialistproduktiviteten. Det finns många fördelar med abstraktion inom teknik som är lite intuitiva – en webbutvecklare behöver inte förstå hur operativsystem fungerar på djupet. 

Nyckeln till att bygga bra, återanvändbara abstraktionslager är att kapsla in komplexiteten hos ett lager och sedan tillhandahålla enkla, men ändå uttrycksfulla gränssnitt för lager högre i stacken att använda. Görs på rätt sätt gör detta det möjligt för utvecklare med olika expertområden och kunskaper att bygga användbara verktyg över hela stacken.

Till ingen överraskning gäller samma principer för zk-system, och dessa abstraktionslager håller på att bli mogna nog för en zk-novis att börja använda dem och bygga applikationer idag.

zk tech-stacken
zk-stacken med några exempel på verktyg/tekniker vid varje lager

Zk-utveckling på låg nivå

Arkworks-rs

Arkworks-rs är ett ekosystem av Rust-bibliotek som tillhandahåller effektiva och säkra implementeringar av underkomponenterna i en zkSNARK-applikation. Arkworks tillhandahåller de gränssnitt som krävs för att utvecklare ska kunna anpassa mjukvarustacken för en zk-applikation utan att behöva implementera om gemensamma drag med andra befintliga bibliotek.

Innan Arkworks var det enda sättet att skapa en ny zk-applikation att bygga allt från grunden. De viktigaste fördelarna med Arkworks-rs jämfört med specialbyggda, vertikalt integrerade verktyg är flexibilitetsnivån, minskningen av duplicerad ingenjörskonst och minskningen av revisionsarbetet. Arkworks förnuftiga gränssnittslinjer mellan komponenter möjliggör en uppgraderingshastighet som kan hålla stacken relevant mitt i den blixtrande innovationstakten inom zk-teknologier, utan att tvinga team att bygga om allt från grunden.

Vem är det till?

Arkworks är för projekt som behöver fin kontroll över hela zk-mjukvarustacken, men som inte vill bygga alla överflödiga bitar från grunden. Om du funderar på en anpassad version av en krets DSL eftersom du till exempel skapar en prototyp av ett nytt provsystem men är osäker på åtagandeschemat eller motsvarande elliptiska kurva, kommer arkworks att tillåta dig att snabbt växla mellan flera alternativ med delade gränssnitt, snarare än att börja från början.

Fördelar

  • Flexibilitet genom modularitet
  • Mindre duplicering av kod
    • Lägre ingenjörskostnader
    • Minskad yta för granskning/bugg
  • Uppgradera vilken komponent som helst utan större omstrukturering
  • Lätt att experimentera med nya primitiver i en snabbt utvecklande zk-miljö

Nackdelar

  • Kräver djup förståelse för hela programvarustacken
    • För mycket kontroll kan leda till fotpistoler om de inte förstås ordentligt
  • Granulär kontroll kräver expertis på alla nivåer i stacken
    • Arkworks tillhandahåller några vettiga standardinställningar.

zk Domain Specific Languages ​​(DSL)

För att kunna skapa ett bevis om någon beräkning måste först denna beräkning uttryckas i en form som ett zkSNARK-system kan förstå. Flera domänspecifika språk har skapat programmeringsspråk som tillåter applikationsutvecklare att uttrycka sina beräkningar på ett sådant sätt. Dessa inkluderar Aztec Noir, Starknets KairoCircomZoKrates, och Aleos Leo bland andra. Det underliggande bevissystemet och de matematiska detaljerna är i allmänhet inte exponerade för applikationsutvecklaren.

Utvecklarupplevelsen

zkApp-utvecklare måste bli skickliga i att skriva sina program på domänspecifika språk. Vissa av dessa språk ser mycket ut som bekanta programmeringsspråk, medan andra kan vara ganska svåra att lära sig. Låt oss bryta ner några av dessa:

Kairo – Starkware DSL krävs för att bygga appar på Starknet. Kompilerar ner till Kairo-specifikt assemblerspråk som kan tolkas av Cairo zkVM.

ZoKrates — ZoKrates är en verktygslåda för vanliga SNARK-behov inklusive ett högnivåspråk för att skriva kretsar. ZoKrates har också viss flexibilitet kring kurvorna, provningsschemat och backend, vilket gör att utvecklare kan hot-swap med enkla CLI-argument.

Circom — Circom är ett specialbyggt språk för att konstruera kretsar. För närvarande är det de-facto språket för kretsar i produktion. Språket är inte speciellt ergonomiskt. Språket i sig gör dig akut medveten om att du skriver kretsar.

Leo — Leo utvecklades som språket för Aleo-blockkedjan. Leo har en viss rostliknande syntax och är speciellt gjord för tillståndsövergångar inuti en blockkedja.

Noir – Rostinspirerad syntax. Byggd kring IR snarare än språket i sig, vilket innebär att det kan ha en godtycklig frontend. 

Aztec Noir-kompileringsstacken har framför allt modulär arkitektur

Vem är det till?

Alla applikationsutvecklare som vill dra nytta av de unika egenskaperna hos zk i sin applikation. Vissa av dessa språk har stridstestats med miljarder dollar som rör sig över dem via kedjor som ZCash och Starknet. Även om några av projekten vi kommer att diskutera inte är helt redo för produktionsanvändning, är att skriva dina kretsar på ett av dessa språk för närvarande den bästa strategin, om du inte behöver de finare kontrollerna som en verktygslåda som Arkworks tillhandahåller.

Fördelar

  • Användare behöver inte förstå de underliggande zk-detaljerna
  • Finns idag med viss produktionserfarenhet
  • Verifierbar på kedja
  • Ekosystemagnostiker

Nackdelar

  • Användare måste lära sig en ny DSL
  • Siled verktyg och support runt vart och ett av dessa språk
  • Lite eller ingen kontroll över den underliggande provningsstacken (för nu)

Det primära målet för en zkEVM är att ta en Ethereum-tillståndsövergång och bevisa dess giltighet med hjälp av ett kortfattat noll-kunskapsbevis på korrekthet. Som nämnts i Vitaliks inlägg finns det ett antal sätt att göra detta med subtila skillnader och motsvarande avvägningar. 

Den huvudsakliga tekniska skillnaden mellan alla dessa är exakt var i språkstacken beräkningen omvandlas till en form (aritmetisering) som kan användas i ett bevissystem. I vissa zkEVMs händer detta på högnivåspråken (Solidity, Vyper, Yul), medan andra metoder försöker bevisa EVM hela vägen till opkodnivån. Avvägningarna mellan dessa tillvägagångssätt täcktes djupt i Vitaliks inlägg, men jag kommer att sammanfatta det i en mening: Ju lägre konverteringen/arithmetiseringen sker i stacken, desto större blir prestationsstraffet.

Varför är EVM-opkoderna dyra att bevisa i zk?

Den största utmaningen med att skapa bevis för en virtuell maskin är att storleken på kretsen växer proportionellt mot storleken på ALLA möjliga instruktioner för varje utförd instruktion. Detta beror på att kretsen inte vet vilka instruktioner som kommer att exekveras i varje program, så den behöver stödja dem alla.

I universella kretsar har varje utförd instruktion en kostnad som är proportionell mot summan av alla stödda instruktioner.

Vad detta innebär i praktiken är att du betalar (i prestationskostnad) för den dyraste möjliga instruktionen, även när du bara utför den enklaste instruktionen. Detta leder till en direkt avvägning mellan generaliserbarhet och prestanda – när du lägger till fler instruktioner för generaliserbarhet betalar du för detta på varje instruktion du bevisar!

Detta är ett grundläggande problem med universella kretsar, men med ny utveckling inom teknik som IVC (incremental verifiable compute), kan denna begränsning förbättras genom att dela upp beräkningen i mindre bitar som var och en har specialiserade, mindre underkretsar.

Dagens zkEVM-implementeringar använder olika strategier för att mildra effekterna av detta problem... Till exempel river zkSync bort de dyrare operationerna (mest kryptografiska förkompileringar som hash och ECDSA) från den huvudsakliga exekveringsbevisande kretsen till separata kretsar som aggregeras tillsammans på sluta via snark-rekursion. zkSync tog detta tillvägagångssätt efter att de insåg att majoriteten av deras kostnader kom från några komplexa instruktioner.

Transaktionskostnaderna domineras av de få dyra operationerna.

Grunden till att det är dyrare att bevisa en mer EVM-ekvivalent instruktionsuppsättning är att EVM inte var designad för zk-beräkningar. Genom att överge EVM tidigare i stacken kan zkEVM köras på instruktionsuppsättningar som är mer optimerade för zk och därmed billigare att bevisa.

Vem är det till?

De idealiska kunderna för en zkEVM är smarta kontraktsapplikationer som behöver billigare transaktioner i storleksordningar än vad som är tillgängligt på L1 Ethereum. Dessa utvecklare har inte nödvändigtvis expertis eller bandbredd för att skriva zk-applikationer från grunden. Därför föredrar de att skriva sina ansökningar på överordnade språk som de är bekanta med, som Solidity. 

Varför bygger så många lag detta?

Skala Ethereum är för närvarande den mest efterfrågade tillämpningen av zk-teknik.

En zkEVM är en Ethereum-skalningslösning som friktionsfritt mildrar problemet med överbelastning som begränsar L1 dApp-utvecklare.

Utvecklarupplevelsen

Målet med en zkEVM är att stödja en utvecklarupplevelse som ligger så nära nuvarande Ethereum-utveckling som möjligt. Fullständigt Solidity-stöd innebär att team inte behöver bygga och underhålla flera kodbaser. Detta är något opraktiskt att göra perfekt eftersom zkEVM:er måste byta ut viss kompatibilitet för att kunna generera bevis av rimlig storlek inom en rimlig tid.

Snabbfallsstudie: zkSync vs Scroll

Den primära skillnaden mellan zkSync och Scroll är var/när i stacken de utför aritmetisering – det vill säga där de konverterar från normala EVM-konstruktioner till en SNARK-vänlig representation. För zkSync händer detta när de konverterar YUL-bytekoden till sin egen anpassade zk-instruktionsuppsättning. För Scroll händer detta i slutet, när den faktiska exekveringsspåret genereras med faktiska EVM-opkoder.

Så för zkSync är allt detsamma som att interagera med EVM tills zk-bytekoden genereras. För Scroll är allt detsamma tills den faktiska bytekoden exekveras. Detta är en subtil skillnad som byter ut prestanda mot support. Till exempel kommer zkSync inte att stödja EVM-bytekodverktyg som en debugger direkt, eftersom det är en helt annan bytekod. Medan Scroll kommer att ha svårare att få bra prestanda ur en instruktionsuppsättning, var den inte designad för zk. Det finns för- och nackdelar med båda strategierna och i slutändan finns det många exogena faktorer som kommer att påverka deras relativa framgång.

zkLLVM kretskompilator

???? Trots namngivningen är LLVM inte en virtuell maskin (VM). LLVM är namnet på en uppsättning kompilatorverktyg som är förankrade av en mellanrepresentation (IR) som är språkagnostisk.

=noll; Foundation (om namnet, det är en SQL-injektion skämt om du undrar) bygger en kompilator som kan konvertera alla LLVM-gränssnittsspråk till en mellanrepresentation som kan bevisas i en SNARK. zkLLVM är utformad som en förlängning av den befintliga LLVM-infrastrukturen, en industristandardverktygskedja som stöder många högnivåspråk som Rust, C, C++ etc.

Hur det fungerar

Grov skiss av zkLLVM-arkitekturen

En användare som vill bevisa någon beräkning skulle helt enkelt implementera den beräkningen i C++. zkLLVM tar denna källkod på hög nivå som stöds av deras modifierade clang-kompilator (för närvarande C++) och genererar en viss mellanrepresentation av kretsen. Vid denna tidpunkt är kretsen redo att bevisas, men användaren kanske vill bevisa kretsen baserat på några dynamiska ingångar. För att hantera dynamiska ingångar har zkLLVM en extra komponent som kallas tilldelaren, som genererar en tilldelningstabell med alla ingångar och vittnen helt förbehandlade och redo att bevisas tillsammans med kretsen.

Dessa 2 komponenter är allt som behövs för att generera ett bevis. En användare kan teoretiskt sett generera ett bevis själv, men eftersom detta är en något specialiserad beräkningsuppgift, kan de vilja betala någon annan, som har hårdvaran, för att göra det åt dem. För denna motpartsupptäcktsmekanism, =noll; Foundation har också etablerat en "bevismarknad" där prover tävlar om att bevisa beräkningar för användare som kommer att betala dem för att göra det. Denna fria marknadsdynamik kommer att leda till att provare optimerar de mest värdefulla provningsuppgifterna.

Avvägningar

Eftersom varje beräkningsuppgift som ska bevisas är unik och genererar en annan krets, finns det ett oändligt antal kretsar som provare kommer att behöva kunna hantera. Denna påtvingade generaliserbarhet gör optimeringen av individuella kretsar svår. Införandet av en provmarknad möjliggör specialisering på de kretsar som marknaden bedömer som värdefulla. Utan denna marknad skulle det vara utmanande att övertyga en provare att optimera denna krets på grund av detta naturliga kallstartproblem.

Den andra avvägningen är den klassiska abstraktionen kontra kontroll. Användare som är villiga att ta det här lättanvända gränssnittet ger upp kontrollen över de underliggande kryptografiska primitiven. För många användare är detta en mycket giltig avvägning att göra, eftersom det ofta är bättre att låta kryptografiexperterna ta dessa beslut åt dig.

Fördelar

  • Användare kan skriva kod på välbekanta högnivåspråk
  • Alla zk internals abstraheras bort från användarna
  • Förlitar sig inte på en specifik "VM"-krets som lägger till ytterligare overhead

Nackdelar

  • Varje program har en annan krets. Svårt att optimera. (bevismarknaden löser detta delvis)
  • Icke-trivialt att byta/uppgradera interna zk-bibliotek (kräver forking)

En zkVM beskriver superuppsättningen av alla zk virtuella maskiner, medan en zkEVM är en specifik typ av zkVM, som var värd att diskutera som ett separat ämne på grund av dess förekomst idag. Det finns några andra projekt som arbetar med att bygga mer generaliserade zkVM:er som är baserade på ISA:er förutom de skräddarsydda krypto-VM:erna.

Istället för att bevisa EVM kan systemet bevisa en annan instruktionsuppsättningsarkitektur (ISA), såsom RISC-V eller WASM i en ny virtuell dator. Två projekt som arbetar med dessa generaliserade zkVMs är RISC Zero och zkWASM. Låt oss dyka in lite i RISC Zero här för att visa hur denna strategi fungerar och några av dess fördelar/nackdelar. 

Risc Zero proof generation högnivåarkitektur

RISC Zero kan bevisa alla beräkningar som exekveras på en RISC-V-arkitektur. RISC-V är en öppen källkodsstandard för instruktionsuppsättningsarkitektur (ISA) som har blivit allt mer populär. RISC-filosofin (reduced instruction set computer) är att bygga en extremt enkel instruktionsuppsättning med minimal komplexitet. Detta innebär att utvecklarna på de högre skikten i stacken slutar ta på sig en större belastning när det gäller att implementera instruktioner med denna arkitektur samtidigt som hårdvaruimplementeringen blir enklare.

Denna filosofi gäller även för allmän datoranvändning, ARM-chips har utnyttjat RISC-stil instruktionsuppsättningar och har börjat dominera marknaden för mobila chips. Det visar sig att de enklare instruktionsuppsättningarna också har större energi- och formareaeffektivitet.

Denna analogi håller ganska bra för effektiviteten i att generera zk-bevis. Som diskuterats tidigare, när du bevisar ett exekveringsspår i zk, betalar du för summan av kostnaden för alla instruktioner per varje objekt i spåret, så enklare och färre totala instruktioner är bättre.

Hur det fungerar

Ur ett utvecklarperspektiv är att använda RISC Zero för att hantera zk-bevis ungefär som att använda AWS Lambda-funktioner för att hantera backend-serverarkitektur. Utvecklare interagerar med RISC Zero eller AWS Lambda genom att helt enkelt skriva kod och tjänsten hanterar all backend-komplexitet.

För RISC Zero skriver utvecklare Rust eller C++ (så småningom allt som är inriktat på RISC-V). Systemet tar sedan ELF-filen som genererades under kompileringen och använder den som indatakod för VM-kretsen. Utvecklare anropar helt enkelt prove som returnerar ett kvitto (som innehåller zk-beviset för exekveringsspåret) som vem som helst kan anropa 'verify' från var som helst. Ur utvecklarens synvinkel finns det inget behov av att förstå hur zk fungerar, det underliggande systemet hanterar all denna komplexitet.

Risc Zero praktikant?

Fördelar

  • Lätt att använda. Öppnar dörren för alla programmerare att bygga zk-applikationer
  • Enkel krets som provare kan specialisera sig på
    • Också mindre yta för attack och mindre att granska
  • Kompatibel med vilken blockchain som helst, du lägger bara upp bevisen

Nackdelar

  • Tar på sig en hel del omkostnader (i provstorlek och generationshastighet) för att stödja ett sådant generiskt gränssnitt
  • Kräver betydande förbättringar av provgenereringstekniker för att uppnå ett brett stöd för befintliga bibliotek

Förbyggda återanvändbara kretsar

För vissa grundläggande och återanvändbara kretsar som är särskilt användbara för blockchain-applikationer eller någon annanstans, kan team redan ha byggt och optimerat dessa kretsar för dig. Du kan bara ge input för ditt specifika användningsfall. Ett Merkle-inkluderingsbevis är till exempel något som vanligtvis behövs i kryptoapplikationer (airdrop-listor, Tornado Cash, etc). Som applikationsutvecklare kan du alltid återanvända dessa stridstestade kontrakt och bara modifiera lagren på toppen för att skapa en unik applikation.

Till exempel kan Tornado Cashs kretsar återanvändas för en privat airdrop-applikation eller ett ansökan om privat röstning. Manta och Semaphore bygger en hel verktygslåda med vanliga kretsprylar som denna som kan användas i Solidity-kontrakt med liten eller ingen förståelse för den underliggande zk-månen.

Guiden — Välj din stack

Som diskuterats ingående finns det en mängd olika alternativ för att utveckla en zk-applikation, alla med sin egen unika uppsättning avvägningar. Det här diagrammet hjälper dig att sammanfatta denna beslutsmatris så att du kan välja det bästa verktyget för jobbet baserat på din nivå av zk-expertis och prestationsbehov. Det här är inte en heltäckande lista, jag planerar att lägga till detta i framtiden när jag blir medveten om att fler verktyg kommer upp i utrymmet.

Applikationsutvecklarens guide till zkGalaxy

fuskblad för zk App Dev

1. Snarkbibliotek på låg nivå

När du ska använda: 

  • Du behöver fin kontroll över hela proverstacken
  • Vill undvika att bygga om vanliga komponenter
  • Du vill experimentera med olika kombinationer av bevisscheman, kurvor och andra lågnivåer primitiver

När du inte ska använda:

  • Du är en nybörjare som letar efter gränssnitt på hög nivå

Alternativ: 


3. zk kompilatorer

När du ska använda: 

  • Ovillig att ta överkostnaderna för en universell krets
  • Vill skriva kretsar på bekanta språk 
  • Behöver mycket anpassad krets

När du inte ska använda: 

  • Vill kontrollera de underliggande kryptografiska primitiven
  • Behöver en krets som redan har blivit kraftigt optimerad

Alternativ:


5. zkVM

När du ska använda: 

  • Vill skriva kod på högnivåspråk 
  • Behöver bevisa riktigheten av denna utförande 
  • Behöver dölja en del av indata till denna exekvering från en verifierare
  • Har liten eller ingen expertis i zk

När du inte ska använda:

  • I miljöer med extremt låg latens (det är fortfarande långsamt)
  • Du har ett enormt program (för nu)

Alternativ:

2. zk DSL

När du ska använda: 

  • Du är bekväm med att ta upp ett nytt språk
  • Vill använda några stridstestade språk
  • Behöver minimal kretsstorlek, villig att avstå från abstraktioner

När du inte ska använda: 

  • Behöver fin kontroll över den beprövade back-end (för nu, kan byta backends för vissa DSLs)

Alternativ:


4. zkEVM

När du ska använda: 

  • Du har en dApp som redan fungerar på EVM
  • Du behöver billigare transaktioner för dina användare 
  • Du vill minimera ansträngningen för att distribuera till en ny kedja
  • Bryr dig bara om den kortfattade egenskapen för zk (komprimering)

När du inte ska använda: 

  • Du behöver perfekt EVM-ekvivalens
  • Du behöver integritetsegenskapen för zk 
  • Du har ett användningsfall som inte är blockchain 

Alternativ: 


6. Förbyggda återanvändbara kretsar

När du ska använda: 

  • Du har en smart kontraktsapplikation som bygger på vanliga zk-byggstenar, som Merkle-inkludering
  • Du har liten eller ingen expertis i de underliggande zk-grejerna

När den inte ska användas:

  • Du har högt specialiserade behov
  • Ditt användningsfall stöds inte av de förbyggda kretsarna 

Alternativ: 

Slutsats

zk är i framkant av flera tekniker, och att bygga den kräver en djupgående förståelse för matematik, kryptografi, datavetenskap och hårdvaruteknik. Ändå, med fler och fler abstraktionslager tillgängliga varje dag, kan apputvecklare utnyttja kraften i zk utan en doktorsexamen. Eftersom begränsningarna för provningstider sakta lyfts över tiden via optimeringar på alla nivåer i stacken, kommer vi sannolikt att se ännu enklare verktyg för den genomsnittliga utvecklaren.

Jag hoppas att jag övertygade dig, den nyfikna mjukvaruutvecklaren, att du kan börja använda zk i dina applikationer idag. Happy Hacking 🙂

vad väntar du på anon, bygg några zk-appar

upplysningar: Blockchain Capital är en investerare i flera av de ovan nämnda protokollen.

Åsikterna som uttrycks i varje blogginlägg kan vara varje författares personliga åsikter och återspeglar inte nödvändigtvis åsikterna från Blockchain Capital och dess dotterbolag. Varken Blockchain Capital eller författaren garanterar riktigheten, adekvatheten eller fullständigheten av informationen som tillhandahålls i varje blogginlägg. Inga utfästelser eller garantier, uttryckliga eller underförstådda, görs eller ges av eller på uppdrag av Blockchain Capital, författaren eller någon annan person beträffande riktigheten och fullständigheten eller rättvisan hos informationen i något blogginlägg och inget ansvar eller ansvar accepteras. för sådan information. Ingenting i varje blogginlägg utgör investerings-, reglerings-, juridiska, efterlevnads- eller skatte- eller andra råd och det är inte heller att lita på det när man fattar ett investeringsbeslut. Blogginlägg ska inte ses som nuvarande eller tidigare rekommendationer eller uppmaningar om ett erbjudande om att köpa eller sälja några värdepapper eller att anta någon investeringsstrategi. Blogginläggen kan innehålla prognoser eller andra framtidsinriktade uttalanden, som är baserade på övertygelser, antaganden och förväntningar som kan förändras som ett resultat av många möjliga händelser eller faktorer. Om en förändring inträffar kan faktiska resultat avvika väsentligt från de som uttrycks i de framåtblickande uttalandena. Alla framåtblickande uttalanden talar endast från det datum sådana uttalanden görs, och varken Blockchain Capital eller varje författare tar på sig någon skyldighet att uppdatera sådana uttalanden förutom vad som krävs enligt lag. I den mån det hänvisas till dokument, presentationer eller annat material som producerats, publicerats eller på annat sätt distribuerats av Blockchain Capital i något blogginlägg, bör sådant material läsas med noggrann uppmärksamhet på eventuella friskrivningar som tillhandahålls där.

Tidsstämpel:

Mer från Blockchain Capital