diff --git a/build.sbt b/build.sbt index 43282a312c..cba74cbe6d 100644 --- a/build.sbt +++ b/build.sbt @@ -78,7 +78,7 @@ version in ThisBuild := { git.gitUncommittedChanges in ThisBuild := true val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.64" -val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.7" +val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.9" val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.6" val macroCompat = "org.typelevel" %% "macro-compat" % "1.1.1" val paradise = "org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full @@ -96,6 +96,7 @@ val testingDependencies = Seq( "org.scalatest" %% "scalatest" % "3.0.5" % "test", "org.scalactic" %% "scalactic" % "3.0.+" % "test", "org.scalacheck" %% "scalacheck" % "1.14.+" % "test", + "com.lihaoyi" %% "pprint" % "0.5.4" % "test", // the last version with Scala 2.11 support "com.storm-enroute" %% "scalameter" % "0.8.2" % Test, "junit" % "junit" % "4.12" % "test", "com.novocode" % "junit-interface" % "0.11" % "test" diff --git a/common/src/main/scala/scalan/TypeDesc.scala b/common/src/main/scala/scalan/TypeDesc.scala index 32e5208bb8..36d56e8f0e 100644 --- a/common/src/main/scala/scalan/TypeDesc.scala +++ b/common/src/main/scala/scalan/TypeDesc.scala @@ -4,7 +4,7 @@ import scala.reflect.ClassTag import scala.annotation.implicitNotFound /** Base type for all runtime type descriptors. */ -@implicitNotFound(msg = "No Elem available for ${A}.") +@implicitNotFound(msg = "No RType available for ${A}.") abstract class RType[A] { /** Class tag suitable for construct instances of Array[A]. */ def classTag: ClassTag[A] diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/src/main/scala/scalan/util/Extensions.scala index d2162d9c7c..072a9226bb 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/src/main/scala/scalan/util/Extensions.scala @@ -48,11 +48,6 @@ object Extensions { * @since 2.0 */ def toAbs: Byte = if (b < 0) (-b).toByte else b - - /** Compares this numeric with that numeric for order. Returns a negative integer, zero, or a positive integer as the - * `this` is less than, equal to, or greater than `that`. - */ - def compare(that: Byte): Byte = if (b < that) -1.toByte else if (b == that) 0.toByte else 1.toByte } implicit class ShortOps(val x: Short) extends AnyVal { @@ -82,6 +77,11 @@ object Extensions { throw new ArithmeticException("Short overflow") r.toShort } + + /** Absolute value of this numeric value. + * @since 2.0 + */ + def toAbs: Short = if (x < 0) (-x).toShort else x } implicit class IntOps(val x: Int) extends AnyVal { @@ -101,11 +101,6 @@ object Extensions { * @since 2.0 */ def toAbs: Int = if (x < 0) -x else x - - /** Compares this numeric with that numeric for order. Returns a negative integer, zero, or a positive integer as the - * `this` is less than, equal to, or greater than `that`. - */ - def compare(that: Int): Int = if (x < that) -1 else if (x == that) 0 else 1 } implicit class LongOps(val x: Long) extends AnyVal { @@ -126,6 +121,11 @@ object Extensions { throw new ArithmeticException("Int overflow") x.toInt } + + /** Absolute value of this numeric value. + * @since 2.0 + */ + def toAbs: Long = if (x < 0) -x else x } implicit class BigIntegerOps(val x: BigInteger) extends AnyVal { diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 61f478da34..5d4e5ffd11 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -297,14 +297,65 @@ class Box { */ def tokens: Coll[(Coll[Byte], Long)] - /** Extracts register by id and type. - * @param regId zero-based identifier of the register. - * @tparam T expected type of the register. - * @return Some(value) if the register is defined and has given type. - * None otherwise - * @since 2.0 - */ - def getReg[T](regId: Int): Option[T] + /** Extracts register by id and type. + * ErgoScript is typed, so accessing a register is an operation which involves some + * expected type given in brackets. Thus `SELF.R4[Int]` expression should evaluate to a + * valid value of the `Option[Int]` type. + * + * For example `val x = SELF.R4[Int]` expects the + * register, if it is present, to have type `Int`. At runtime the corresponding type + * descriptor is passed as `implicit t: RType[T]` parameter of `getReg` method and + * checked against the actual value of the register. + * + * There are three cases: + * 1) If the register doesn't exist. + * Then `val x = SELF.R4[Int]` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the register contains a value `v` of type `Int`. + * Then `val x = SELF.R4[Int]` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the register contains a value `v` of type T other then `Int`. + * Then `val x = SELF.R4[Int]` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of register is present, so returning it as None + * would break the typed semantics of registers collection. + * + * In some use cases one register may have values of different types. To access such + * register an additional register can be used as a tag. + * + *
+ * val tagOpt = SELF.R5[Int] + * val res = if (tagOpt.isDefined) { + * val tag = tagOpt.get + * if (tag == 1) { + * val x = SELF.R4[Int].get + * // compute res using value x is of type Int + * } else if (tag == 2) { + * val x = SELF.R4[GroupElement].get + * // compute res using value x is of type GroupElement + * } else if (tag == 3) { + * val x = SELF.R4[ Array[Byte] ].get + * // compute res using value x of type Array[Byte] + * } else { + * // compute `res` when `tag` is not 1, 2 or 3 + * } + * } + * else { + * // compute value of res when register is not present + * } + *+ * + * @param i zero-based identifier of the register. + * @tparam T expected type of the register. + * @return Some(value) if the register is defined AND has the given type. + * None otherwise + * @throws special.sigma.InvalidType exception when the type of the register value is + * different from T. + * @since 2.0 + */ + def getReg[T](i: Int): Option[T] /** Extracts register as Coll[Byte], deserializes it to script and then executes this script in the current context. * The original Coll[Byte] of the script is available as getReg[Coll[Byte]](id) diff --git a/docs/posters/fc-poster-2.pdf b/docs/posters/fc-poster-2.pdf new file mode 100644 index 0000000000..55f615df2b Binary files /dev/null and b/docs/posters/fc-poster-2.pdf differ diff --git a/docs/posters/llncs.cls b/docs/posters/llncs.cls new file mode 100644 index 0000000000..1d49f3d238 --- /dev/null +++ b/docs/posters/llncs.cls @@ -0,0 +1,1207 @@ +% LLNCS DOCUMENT CLASS -- version 2.17 (12-Jul-2010) +% Springer Verlag LaTeX2e support for Lecture Notes in Computer Science +% +%% +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\NeedsTeXFormat{LaTeX2e}[1995/12/01] +\ProvidesClass{llncs}[2010/07/12 v2.17 +^^J LaTeX document class for Lecture Notes in Computer Science] +% Options +\let\if@envcntreset\iffalse +\DeclareOption{envcountreset}{\let\if@envcntreset\iftrue} +\DeclareOption{citeauthoryear}{\let\citeauthoryear=Y} +\DeclareOption{oribibl}{\let\oribibl=Y} +\let\if@custvec\iftrue +\DeclareOption{orivec}{\let\if@custvec\iffalse} +\let\if@envcntsame\iffalse +\DeclareOption{envcountsame}{\let\if@envcntsame\iftrue} +\let\if@envcntsect\iffalse +\DeclareOption{envcountsect}{\let\if@envcntsect\iftrue} +\let\if@runhead\iffalse +\DeclareOption{runningheads}{\let\if@runhead\iftrue} + +\let\if@openright\iftrue +\let\if@openbib\iffalse +\DeclareOption{openbib}{\let\if@openbib\iftrue} + +% languages +\let\switcht@@therlang\relax +\def\ds@deutsch{\def\switcht@@therlang{\switcht@deutsch}} +\def\ds@francais{\def\switcht@@therlang{\switcht@francais}} + +\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} + +\ProcessOptions + +\LoadClass[twoside]{article} +\RequirePackage{multicol} % needed for the list of participants, index +\RequirePackage{aliascnt} + +\setlength{\textwidth}{12.2cm} +\setlength{\textheight}{19.3cm} +\renewcommand\@pnumwidth{2em} +\renewcommand\@tocrmarg{3.5em} +% +\def\@dottedtocline#1#2#3#4#5{% + \ifnum #1>\c@tocdepth \else + \vskip \z@ \@plus.2\p@ + {\leftskip #2\relax \rightskip \@tocrmarg \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \parindent #2\relax\@afterindenttrue + \interlinepenalty\@M + \leavevmode + \@tempdima #3\relax + \advance\leftskip \@tempdima \null\nobreak\hskip -\leftskip + {#4}\nobreak + \leaders\hbox{$\m@th + \mkern \@dotsep mu\hbox{.}\mkern \@dotsep + mu$}\hfill + \nobreak + \hb@xt@\@pnumwidth{\hfil\normalfont \normalcolor #5}% + \par}% + \fi} +% +\def\switcht@albion{% +\def\abstractname{Abstract.} +\def\ackname{Acknowledgement.} +\def\andname{and} +\def\lastandname{\unskip, and} +\def\appendixname{Appendix} +\def\chaptername{Chapter} +\def\claimname{Claim} +\def\conjecturename{Conjecture} +\def\contentsname{Table of Contents} +\def\corollaryname{Corollary} +\def\definitionname{Definition} +\def\examplename{Example} +\def\exercisename{Exercise} +\def\figurename{Fig.} +\def\keywordname{{\bf Keywords:}} +\def\indexname{Index} +\def\lemmaname{Lemma} +\def\contriblistname{List of Contributors} +\def\listfigurename{List of Figures} +\def\listtablename{List of Tables} +\def\mailname{{\it Correspondence to\/}:} +\def\noteaddname{Note added in proof} +\def\notename{Note} +\def\partname{Part} +\def\problemname{Problem} +\def\proofname{Proof} +\def\propertyname{Property} +\def\propositionname{Proposition} +\def\questionname{Question} +\def\remarkname{Remark} +\def\seename{see} +\def\solutionname{Solution} +\def\subclassname{{\it Subject Classifications\/}:} +\def\tablename{Table} +\def\theoremname{Theorem}} +\switcht@albion +% Names of theorem like environments are already defined +% but must be translated if another language is chosen +% +% French section +\def\switcht@francais{%\typeout{On parle francais.}% + \def\abstractname{R\'esum\'e.}% + \def\ackname{Remerciements.}% + \def\andname{et}% + \def\lastandname{ et}% + \def\appendixname{Appendice} + \def\chaptername{Chapitre}% + \def\claimname{Pr\'etention}% + \def\conjecturename{Hypoth\`ese}% + \def\contentsname{Table des mati\`eres}% + \def\corollaryname{Corollaire}% + \def\definitionname{D\'efinition}% + \def\examplename{Exemple}% + \def\exercisename{Exercice}% + \def\figurename{Fig.}% + \def\keywordname{{\bf Mots-cl\'e:}} + \def\indexname{Index} + \def\lemmaname{Lemme}% + \def\contriblistname{Liste des contributeurs} + \def\listfigurename{Liste des figures}% + \def\listtablename{Liste des tables}% + \def\mailname{{\it Correspondence to\/}:} + \def\noteaddname{Note ajout\'ee \`a l'\'epreuve}% + \def\notename{Remarque}% + \def\partname{Partie}% + \def\problemname{Probl\`eme}% + \def\proofname{Preuve}% + \def\propertyname{Caract\'eristique}% +%\def\propositionname{Proposition}% + \def\questionname{Question}% + \def\remarkname{Remarque}% + \def\seename{voir} + \def\solutionname{Solution}% + \def\subclassname{{\it Subject Classifications\/}:} + \def\tablename{Tableau}% + \def\theoremname{Th\'eor\`eme}% +} +% +% German section +\def\switcht@deutsch{%\typeout{Man spricht deutsch.}% + \def\abstractname{Zusammenfassung.}% + \def\ackname{Danksagung.}% + \def\andname{und}% + \def\lastandname{ und}% + \def\appendixname{Anhang}% + \def\chaptername{Kapitel}% + \def\claimname{Behauptung}% + \def\conjecturename{Hypothese}% + \def\contentsname{Inhaltsverzeichnis}% + \def\corollaryname{Korollar}% +%\def\definitionname{Definition}% + \def\examplename{Beispiel}% + \def\exercisename{\"Ubung}% + \def\figurename{Abb.}% + \def\keywordname{{\bf Schl\"usselw\"orter:}} + \def\indexname{Index} +%\def\lemmaname{Lemma}% + \def\contriblistname{Mitarbeiter} + \def\listfigurename{Abbildungsverzeichnis}% + \def\listtablename{Tabellenverzeichnis}% + \def\mailname{{\it Correspondence to\/}:} + \def\noteaddname{Nachtrag}% + \def\notename{Anmerkung}% + \def\partname{Teil}% +%\def\problemname{Problem}% + \def\proofname{Beweis}% + \def\propertyname{Eigenschaft}% +%\def\propositionname{Proposition}% + \def\questionname{Frage}% + \def\remarkname{Anmerkung}% + \def\seename{siehe} + \def\solutionname{L\"osung}% + \def\subclassname{{\it Subject Classifications\/}:} + \def\tablename{Tabelle}% +%\def\theoremname{Theorem}% +} + +% Ragged bottom for the actual page +\def\thisbottomragged{\def\@textbottom{\vskip\z@ plus.0001fil +\global\let\@textbottom\relax}} + +\renewcommand\small{% + \@setfontsize\small\@ixpt{11}% + \abovedisplayskip 8.5\p@ \@plus3\p@ \@minus4\p@ + \abovedisplayshortskip \z@ \@plus2\p@ + \belowdisplayshortskip 4\p@ \@plus2\p@ \@minus2\p@ + \def\@listi{\leftmargin\leftmargini + \parsep 0\p@ \@plus1\p@ \@minus\p@ + \topsep 8\p@ \@plus2\p@ \@minus4\p@ + \itemsep0\p@}% + \belowdisplayskip \abovedisplayskip +} + +\frenchspacing +\widowpenalty=10000 +\clubpenalty=10000 + +\setlength\oddsidemargin {63\p@} +\setlength\evensidemargin {63\p@} +\setlength\marginparwidth {90\p@} + +\setlength\headsep {16\p@} + +\setlength\footnotesep{7.7\p@} +\setlength\textfloatsep{8mm\@plus 2\p@ \@minus 4\p@} +\setlength\intextsep {8mm\@plus 2\p@ \@minus 2\p@} + +\setcounter{secnumdepth}{2} + +\newcounter {chapter} +\renewcommand\thechapter {\@arabic\c@chapter} + +\newif\if@mainmatter \@mainmattertrue +\newcommand\frontmatter{\cleardoublepage + \@mainmatterfalse\pagenumbering{Roman}} +\newcommand\mainmatter{\cleardoublepage + \@mainmattertrue\pagenumbering{arabic}} +\newcommand\backmatter{\if@openright\cleardoublepage\else\clearpage\fi + \@mainmatterfalse} + +\renewcommand\part{\cleardoublepage + \thispagestyle{empty}% + \if@twocolumn + \onecolumn + \@tempswatrue + \else + \@tempswafalse + \fi + \null\vfil + \secdef\@part\@spart} + +\def\@part[#1]#2{% + \ifnum \c@secnumdepth >-2\relax + \refstepcounter{part}% + \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% + \else + \addcontentsline{toc}{part}{#1}% + \fi + \markboth{}{}% + {\centering + \interlinepenalty \@M + \normalfont + \ifnum \c@secnumdepth >-2\relax + \huge\bfseries \partname~\thepart + \par + \vskip 20\p@ + \fi + \Huge \bfseries #2\par}% + \@endpart} +\def\@spart#1{% + {\centering + \interlinepenalty \@M + \normalfont + \Huge \bfseries #1\par}% + \@endpart} +\def\@endpart{\vfil\newpage + \if@twoside + \null + \thispagestyle{empty}% + \newpage + \fi + \if@tempswa + \twocolumn + \fi} + +\newcommand\chapter{\clearpage + \thispagestyle{empty}% + \global\@topnum\z@ + \@afterindentfalse + \secdef\@chapter\@schapter} +\def\@chapter[#1]#2{\ifnum \c@secnumdepth >\m@ne + \if@mainmatter + \refstepcounter{chapter}% + \typeout{\@chapapp\space\thechapter.}% + \addcontentsline{toc}{chapter}% + {\protect\numberline{\thechapter}#1}% + \else + \addcontentsline{toc}{chapter}{#1}% + \fi + \else + \addcontentsline{toc}{chapter}{#1}% + \fi + \chaptermark{#1}% + \addtocontents{lof}{\protect\addvspace{10\p@}}% + \addtocontents{lot}{\protect\addvspace{10\p@}}% + \if@twocolumn + \@topnewpage[\@makechapterhead{#2}]% + \else + \@makechapterhead{#2}% + \@afterheading + \fi} +\def\@makechapterhead#1{% +% \vspace*{50\p@}% + {\centering + \ifnum \c@secnumdepth >\m@ne + \if@mainmatter + \large\bfseries \@chapapp{} \thechapter + \par\nobreak + \vskip 20\p@ + \fi + \fi + \interlinepenalty\@M + \Large \bfseries #1\par\nobreak + \vskip 40\p@ + }} +\def\@schapter#1{\if@twocolumn + \@topnewpage[\@makeschapterhead{#1}]% + \else + \@makeschapterhead{#1}% + \@afterheading + \fi} +\def\@makeschapterhead#1{% +% \vspace*{50\p@}% + {\centering + \normalfont + \interlinepenalty\@M + \Large \bfseries #1\par\nobreak + \vskip 40\p@ + }} + +\renewcommand\section{\@startsection{section}{1}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {12\p@ \@plus 4\p@ \@minus 4\p@}% + {\normalfont\large\bfseries\boldmath + \rightskip=\z@ \@plus 8em\pretolerance=10000 }} +\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {8\p@ \@plus 4\p@ \@minus 4\p@}% + {\normalfont\normalsize\bfseries\boldmath + \rightskip=\z@ \@plus 8em\pretolerance=10000 }} +\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {-0.5em \@plus -0.22em \@minus -0.1em}% + {\normalfont\normalsize\bfseries\boldmath}} +\renewcommand\paragraph{\@startsection{paragraph}{4}{\z@}% + {-12\p@ \@plus -4\p@ \@minus -4\p@}% + {-0.5em \@plus -0.22em \@minus -0.1em}% + {\normalfont\normalsize\itshape}} +\renewcommand\subparagraph[1]{\typeout{LLNCS warning: You should not use + \string\subparagraph\space with this class}\vskip0.5cm +You should not use \verb|\subparagraph| with this class.\vskip0.5cm} + +\DeclareMathSymbol{\Gamma}{\mathalpha}{letters}{"00} +\DeclareMathSymbol{\Delta}{\mathalpha}{letters}{"01} +\DeclareMathSymbol{\Theta}{\mathalpha}{letters}{"02} +\DeclareMathSymbol{\Lambda}{\mathalpha}{letters}{"03} +\DeclareMathSymbol{\Xi}{\mathalpha}{letters}{"04} +\DeclareMathSymbol{\Pi}{\mathalpha}{letters}{"05} +\DeclareMathSymbol{\Sigma}{\mathalpha}{letters}{"06} +\DeclareMathSymbol{\Upsilon}{\mathalpha}{letters}{"07} +\DeclareMathSymbol{\Phi}{\mathalpha}{letters}{"08} +\DeclareMathSymbol{\Psi}{\mathalpha}{letters}{"09} +\DeclareMathSymbol{\Omega}{\mathalpha}{letters}{"0A} + +\let\footnotesize\small + +\if@custvec +\def\vec#1{\mathchoice{\mbox{\boldmath$\displaystyle#1$}} +{\mbox{\boldmath$\textstyle#1$}} +{\mbox{\boldmath$\scriptstyle#1$}} +{\mbox{\boldmath$\scriptscriptstyle#1$}}} +\fi + +\def\squareforqed{\hbox{\rlap{$\sqcap$}$\sqcup$}} +\def\qed{\ifmmode\squareforqed\else{\unskip\nobreak\hfil +\penalty50\hskip1em\null\nobreak\hfil\squareforqed +\parfillskip=0pt\finalhyphendemerits=0\endgraf}\fi} + +\def\getsto{\mathrel{\mathchoice {\vcenter{\offinterlineskip +\halign{\hfil +$\displaystyle##$\hfil\cr\gets\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr\gets +\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr\gets +\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +\gets\cr\to\cr}}}}} +\def\lid{\mathrel{\mathchoice {\vcenter{\offinterlineskip\halign{\hfil +$\displaystyle##$\hfil\cr<\cr\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr<\cr +\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr<\cr +\noalign{\vskip1pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +<\cr +\noalign{\vskip0.9pt}=\cr}}}}} +\def\gid{\mathrel{\mathchoice {\vcenter{\offinterlineskip\halign{\hfil +$\displaystyle##$\hfil\cr>\cr\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr>\cr +\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr>\cr +\noalign{\vskip1pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +>\cr +\noalign{\vskip0.9pt}=\cr}}}}} +\def\grole{\mathrel{\mathchoice {\vcenter{\offinterlineskip +\halign{\hfil +$\displaystyle##$\hfil\cr>\cr\noalign{\vskip-1pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr +>\cr\noalign{\vskip-1pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr +>\cr\noalign{\vskip-0.8pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +>\cr\noalign{\vskip-0.3pt}<\cr}}}}} +\def\bbbr{{\rm I\!R}} %reelle Zahlen +\def\bbbm{{\rm I\!M}} +\def\bbbn{{\rm I\!N}} %natuerliche Zahlen +\def\bbbf{{\rm I\!F}} +\def\bbbh{{\rm I\!H}} +\def\bbbk{{\rm I\!K}} +\def\bbbp{{\rm I\!P}} +\def\bbbone{{\mathchoice {\rm 1\mskip-4mu l} {\rm 1\mskip-4mu l} +{\rm 1\mskip-4.5mu l} {\rm 1\mskip-5mu l}}} +\def\bbbc{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}}}} +\def\bbbq{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm +Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.8\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.8\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.7\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.7\ht0\hss}\box0}}}} +\def\bbbt{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm +T$}\hbox{\hbox to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}}}} +\def\bbbs{{\mathchoice +{\setbox0=\hbox{$\displaystyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\hbox +to0pt{\kern0.55\wd0\vrule height0.5\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\hbox +to0pt{\kern0.55\wd0\vrule height0.5\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\raise0.05\ht0\hbox +to0pt{\kern0.5\wd0\vrule height0.45\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.4\wd0\vrule height0.45\ht0\hss}\raise0.05\ht0\hbox +to0pt{\kern0.55\wd0\vrule height0.45\ht0\hss}\box0}}}} +\def\bbbz{{\mathchoice {\hbox{$\mathsf\textstyle Z\kern-0.4em Z$}} +{\hbox{$\mathsf\textstyle Z\kern-0.4em Z$}} +{\hbox{$\mathsf\scriptstyle Z\kern-0.3em Z$}} +{\hbox{$\mathsf\scriptscriptstyle Z\kern-0.2em Z$}}}} + +\let\ts\, + +\setlength\leftmargini {17\p@} +\setlength\leftmargin {\leftmargini} +\setlength\leftmarginii {\leftmargini} +\setlength\leftmarginiii {\leftmargini} +\setlength\leftmarginiv {\leftmargini} +\setlength \labelsep {.5em} +\setlength \labelwidth{\leftmargini} +\addtolength\labelwidth{-\labelsep} + +\def\@listI{\leftmargin\leftmargini + \parsep 0\p@ \@plus1\p@ \@minus\p@ + \topsep 8\p@ \@plus2\p@ \@minus4\p@ + \itemsep0\p@} +\let\@listi\@listI +\@listi +\def\@listii {\leftmargin\leftmarginii + \labelwidth\leftmarginii + \advance\labelwidth-\labelsep + \topsep 0\p@ \@plus2\p@ \@minus\p@} +\def\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii + \advance\labelwidth-\labelsep + \topsep 0\p@ \@plus\p@\@minus\p@ + \parsep \z@ + \partopsep \p@ \@plus\z@ \@minus\p@} + +\renewcommand\labelitemi{\normalfont\bfseries --} +\renewcommand\labelitemii{$\m@th\bullet$} + +\setlength\arraycolsep{1.4\p@} +\setlength\tabcolsep{1.4\p@} + +\def\tableofcontents{\chapter*{\contentsname\@mkboth{{\contentsname}}% + {{\contentsname}}} + \def\authcount##1{\setcounter{auco}{##1}\setcounter{@auth}{1}} + \def\lastand{\ifnum\value{auco}=2\relax + \unskip{} \andname\ + \else + \unskip \lastandname\ + \fi}% + \def\and{\stepcounter{@auth}\relax + \ifnum\value{@auth}=\value{auco}% + \lastand + \else + \unskip, + \fi}% + \@starttoc{toc}\if@restonecol\twocolumn\fi} + +\def\l@part#1#2{\addpenalty{\@secpenalty}% + \addvspace{2em plus\p@}% % space above part line + \begingroup + \parindent \z@ + \rightskip \z@ plus 5em + \hrule\vskip5pt + \large % same size as for a contribution heading + \bfseries\boldmath % set line in boldface + \leavevmode % TeX command to enter horizontal mode. + #1\par + \vskip5pt + \hrule + \vskip1pt + \nobreak % Never break after part entry + \endgroup} + +\def\@dotsep{2} + +\let\phantomsection=\relax + +\def\hyperhrefextend{\ifx\hyper@anchor\@undefined\else +{}\fi} + +\def\addnumcontentsmark#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{\protect\numberline + {\thechapter}#3}{\thepage}\hyperhrefextend}}% +\def\addcontentsmark#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{#3}{\thepage}\hyperhrefextend}}% +\def\addcontentsmarkwop#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{#3}{0}\hyperhrefextend}}% + +\def\@adcmk[#1]{\ifcase #1 \or +\def\@gtempa{\addnumcontentsmark}% + \or \def\@gtempa{\addcontentsmark}% + \or \def\@gtempa{\addcontentsmarkwop}% + \fi\@gtempa{toc}{chapter}% +} +\def\addtocmark{% +\phantomsection +\@ifnextchar[{\@adcmk}{\@adcmk[3]}% +} + +\def\l@chapter#1#2{\addpenalty{-\@highpenalty} + \vskip 1.0em plus 1pt \@tempdima 1.5em \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima \hskip -\leftskip + {\large\bfseries\boldmath#1}\ifx0#2\hfil\null + \else + \nobreak + \leaders\hbox{$\m@th \mkern \@dotsep mu.\mkern + \@dotsep mu$}\hfill + \nobreak\hbox to\@pnumwidth{\hss #2}% + \fi\par + \penalty\@highpenalty \endgroup} + +\def\l@title#1#2{\addpenalty{-\@highpenalty} + \addvspace{8pt plus 1pt} + \@tempdima \z@ + \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima \hskip -\leftskip + #1\nobreak + \leaders\hbox{$\m@th \mkern \@dotsep mu.\mkern + \@dotsep mu$}\hfill + \nobreak\hbox to\@pnumwidth{\hss #2}\par + \penalty\@highpenalty \endgroup} + +\def\l@author#1#2{\addpenalty{\@highpenalty} + \@tempdima=15\p@ %\z@ + \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima %\hskip -\leftskip + \textit{#1}\par + \penalty\@highpenalty \endgroup} + +\setcounter{tocdepth}{0} +\newdimen\tocchpnum +\newdimen\tocsecnum +\newdimen\tocsectotal +\newdimen\tocsubsecnum +\newdimen\tocsubsectotal +\newdimen\tocsubsubsecnum +\newdimen\tocsubsubsectotal +\newdimen\tocparanum +\newdimen\tocparatotal +\newdimen\tocsubparanum +\tocchpnum=\z@ % no chapter numbers +\tocsecnum=15\p@ % section 88. plus 2.222pt +\tocsubsecnum=23\p@ % subsection 88.8 plus 2.222pt +\tocsubsubsecnum=27\p@ % subsubsection 88.8.8 plus 1.444pt +\tocparanum=35\p@ % paragraph 88.8.8.8 plus 1.666pt +\tocsubparanum=43\p@ % subparagraph 88.8.8.8.8 plus 1.888pt +\def\calctocindent{% +\tocsectotal=\tocchpnum +\advance\tocsectotal by\tocsecnum +\tocsubsectotal=\tocsectotal +\advance\tocsubsectotal by\tocsubsecnum +\tocsubsubsectotal=\tocsubsectotal +\advance\tocsubsubsectotal by\tocsubsubsecnum +\tocparatotal=\tocsubsubsectotal +\advance\tocparatotal by\tocparanum} +\calctocindent + +\def\l@section{\@dottedtocline{1}{\tocchpnum}{\tocsecnum}} +\def\l@subsection{\@dottedtocline{2}{\tocsectotal}{\tocsubsecnum}} +\def\l@subsubsection{\@dottedtocline{3}{\tocsubsectotal}{\tocsubsubsecnum}} +\def\l@paragraph{\@dottedtocline{4}{\tocsubsubsectotal}{\tocparanum}} +\def\l@subparagraph{\@dottedtocline{5}{\tocparatotal}{\tocsubparanum}} + +\def\listoffigures{\@restonecolfalse\if@twocolumn\@restonecoltrue\onecolumn + \fi\section*{\listfigurename\@mkboth{{\listfigurename}}{{\listfigurename}}} + \@starttoc{lof}\if@restonecol\twocolumn\fi} +\def\l@figure{\@dottedtocline{1}{0em}{1.5em}} + +\def\listoftables{\@restonecolfalse\if@twocolumn\@restonecoltrue\onecolumn + \fi\section*{\listtablename\@mkboth{{\listtablename}}{{\listtablename}}} + \@starttoc{lot}\if@restonecol\twocolumn\fi} +\let\l@table\l@figure + +\renewcommand\listoffigures{% + \section*{\listfigurename + \@mkboth{\listfigurename}{\listfigurename}}% + \@starttoc{lof}% + } + +\renewcommand\listoftables{% + \section*{\listtablename + \@mkboth{\listtablename}{\listtablename}}% + \@starttoc{lot}% + } + +\ifx\oribibl\undefined +\ifx\citeauthoryear\undefined +\renewenvironment{thebibliography}[1] + {\section*{\refname} + \def\@biblabel##1{##1.} + \small + \list{\@biblabel{\@arabic\c@enumiv}}% + {\settowidth\labelwidth{\@biblabel{#1}}% + \leftmargin\labelwidth + \advance\leftmargin\labelsep + \if@openbib + \advance\leftmargin\bibindent + \itemindent -\bibindent + \listparindent \itemindent + \parsep \z@ + \fi + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \if@openbib + \renewcommand\newblock{\par}% + \else + \renewcommand\newblock{\hskip .11em \@plus.33em \@minus.07em}% + \fi + \sloppy\clubpenalty4000\widowpenalty4000% + \sfcode`\.=\@m} + {\def\@noitemerr + {\@latex@warning{Empty `thebibliography' environment}}% + \endlist} +\def\@lbibitem[#1]#2{\item[{[#1]}\hfill]\if@filesw + {\let\protect\noexpand\immediate + \write\@auxout{\string\bibcite{#2}{#1}}}\fi\ignorespaces} +\newcount\@tempcntc +\def\@citex[#1]#2{\if@filesw\immediate\write\@auxout{\string\citation{#2}}\fi + \@tempcnta\z@\@tempcntb\m@ne\def\@citea{}\@cite{\@for\@citeb:=#2\do + {\@ifundefined + {b@\@citeb}{\@citeo\@tempcntb\m@ne\@citea\def\@citea{,}{\bfseries + ?}\@warning + {Citation `\@citeb' on page \thepage \space undefined}}% + {\setbox\z@\hbox{\global\@tempcntc0\csname b@\@citeb\endcsname\relax}% + \ifnum\@tempcntc=\z@ \@citeo\@tempcntb\m@ne + \@citea\def\@citea{,}\hbox{\csname b@\@citeb\endcsname}% + \else + \advance\@tempcntb\@ne + \ifnum\@tempcntb=\@tempcntc + \else\advance\@tempcntb\m@ne\@citeo + \@tempcnta\@tempcntc\@tempcntb\@tempcntc\fi\fi}}\@citeo}{#1}} +\def\@citeo{\ifnum\@tempcnta>\@tempcntb\else + \@citea\def\@citea{,\,\hskip\z@skip}% + \ifnum\@tempcnta=\@tempcntb\the\@tempcnta\else + {\advance\@tempcnta\@ne\ifnum\@tempcnta=\@tempcntb \else + \def\@citea{--}\fi + \advance\@tempcnta\m@ne\the\@tempcnta\@citea\the\@tempcntb}\fi\fi} +\else +\renewenvironment{thebibliography}[1] + {\section*{\refname} + \small + \list{}% + {\settowidth\labelwidth{}% + \leftmargin\parindent + \itemindent=-\parindent + \labelsep=\z@ + \if@openbib + \advance\leftmargin\bibindent + \itemindent -\bibindent + \listparindent \itemindent + \parsep \z@ + \fi + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{}}% + \if@openbib + \renewcommand\newblock{\par}% + \else + \renewcommand\newblock{\hskip .11em \@plus.33em \@minus.07em}% + \fi + \sloppy\clubpenalty4000\widowpenalty4000% + \sfcode`\.=\@m} + {\def\@noitemerr + {\@latex@warning{Empty `thebibliography' environment}}% + \endlist} + \def\@cite#1{#1}% + \def\@lbibitem[#1]#2{\item[]\if@filesw + {\def\protect##1{\string ##1\space}\immediate + \write\@auxout{\string\bibcite{#2}{#1}}}\fi\ignorespaces} + \fi +\else +\@cons\@openbib@code{\noexpand\small} +\fi + +\def\idxquad{\hskip 10\p@}% space that divides entry from number + +\def\@idxitem{\par\hangindent 10\p@} + +\def\subitem{\par\setbox0=\hbox{--\enspace}% second order + \noindent\hangindent\wd0\box0}% index entry + +\def\subsubitem{\par\setbox0=\hbox{--\,--\enspace}% third + \noindent\hangindent\wd0\box0}% order index entry + +\def\indexspace{\par \vskip 10\p@ plus5\p@ minus3\p@\relax} + +\renewenvironment{theindex} + {\@mkboth{\indexname}{\indexname}% + \thispagestyle{empty}\parindent\z@ + \parskip\z@ \@plus .3\p@\relax + \let\item\par + \def\,{\relax\ifmmode\mskip\thinmuskip + \else\hskip0.2em\ignorespaces\fi}% + \normalfont\small + \begin{multicols}{2}[\@makeschapterhead{\indexname}]% + } + {\end{multicols}} + +\renewcommand\footnoterule{% + \kern-3\p@ + \hrule\@width 2truecm + \kern2.6\p@} + \newdimen\fnindent + \fnindent1em +\long\def\@makefntext#1{% + \parindent \fnindent% + \leftskip \fnindent% + \noindent + \llap{\hb@xt@1em{\hss\@makefnmark\ }}\ignorespaces#1} + +\long\def\@makecaption#1#2{% + \small + \vskip\abovecaptionskip + \sbox\@tempboxa{{\bfseries #1.} #2}% + \ifdim \wd\@tempboxa >\hsize + {\bfseries #1.} #2\par + \else + \global \@minipagefalse + \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}% + \fi + \vskip\belowcaptionskip} + +\def\fps@figure{htbp} +\def\fnum@figure{\figurename\thinspace\thefigure} +\def \@floatboxreset {% + \reset@font + \small + \@setnobreak + \@setminipage +} +\def\fps@table{htbp} +\def\fnum@table{\tablename~\thetable} +\renewenvironment{table} + {\setlength\abovecaptionskip{0\p@}% + \setlength\belowcaptionskip{10\p@}% + \@float{table}} + {\end@float} +\renewenvironment{table*} + {\setlength\abovecaptionskip{0\p@}% + \setlength\belowcaptionskip{10\p@}% + \@dblfloat{table}} + {\end@dblfloat} + +\long\def\@caption#1[#2]#3{\par\addcontentsline{\csname + ext@#1\endcsname}{#1}{\protect\numberline{\csname + the#1\endcsname}{\ignorespaces #2}}\begingroup + \@parboxrestore + \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par + \endgroup} + +% LaTeX does not provide a command to enter the authors institute +% addresses. The \institute command is defined here. + +\newcounter{@inst} +\newcounter{@auth} +\newcounter{auco} +\newdimen\instindent +\newbox\authrun +\newtoks\authorrunning +\newtoks\tocauthor +\newbox\titrun +\newtoks\titlerunning +\newtoks\toctitle + +\def\clearheadinfo{\gdef\@author{No Author Given}% + \gdef\@title{No Title Given}% + \gdef\@subtitle{}% + \gdef\@institute{No Institute Given}% + \gdef\@thanks{}% + \global\titlerunning={}\global\authorrunning={}% + \global\toctitle={}\global\tocauthor={}} + +\def\institute#1{\gdef\@institute{#1}} + +\def\institutename{\par + \begingroup + \parskip=\z@ + \parindent=\z@ + \setcounter{@inst}{1}% + \def\and{\par\stepcounter{@inst}% + \noindent$^{\the@inst}$\enspace\ignorespaces}% + \setbox0=\vbox{\def\thanks##1{}\@institute}% + \ifnum\c@@inst=1\relax + \gdef\fnnstart{0}% + \else + \xdef\fnnstart{\c@@inst}% + \setcounter{@inst}{1}% + \noindent$^{\the@inst}$\enspace + \fi + \ignorespaces + \@institute\par + \endgroup} + +\def\@fnsymbol#1{\ensuremath{\ifcase#1\or\star\or{\star\star}\or + {\star\star\star}\or \dagger\or \ddagger\or + \mathchar "278\or \mathchar "27B\or \|\or **\or \dagger\dagger + \or \ddagger\ddagger \else\@ctrerr\fi}} + +\def\inst#1{\unskip$^{#1}$} +\def\fnmsep{\unskip$^,$} +\def\email#1{{\tt#1}} +\AtBeginDocument{\@ifundefined{url}{\def\url#1{#1}}{}% +\@ifpackageloaded{babel}{% +\@ifundefined{extrasenglish}{}{\addto\extrasenglish{\switcht@albion}}% +\@ifundefined{extrasfrenchb}{}{\addto\extrasfrenchb{\switcht@francais}}% +\@ifundefined{extrasgerman}{}{\addto\extrasgerman{\switcht@deutsch}}% +}{\switcht@@therlang}% +\providecommand{\keywords}[1]{\par\addvspace\baselineskip +\noindent\keywordname\enspace\ignorespaces#1}% +} +\def\homedir{\~{ }} + +\def\subtitle#1{\gdef\@subtitle{#1}} +\clearheadinfo +% +%%% to avoid hyperref warnings +\providecommand*{\toclevel@author}{999} +%%% to make title-entry parent of section-entries +\providecommand*{\toclevel@title}{0} +% +\renewcommand\maketitle{\newpage +\phantomsection + \refstepcounter{chapter}% + \stepcounter{section}% + \setcounter{section}{0}% + \setcounter{subsection}{0}% + \setcounter{figure}{0} + \setcounter{table}{0} + \setcounter{equation}{0} + \setcounter{footnote}{0}% + \begingroup + \parindent=\z@ + \renewcommand\thefootnote{\@fnsymbol\c@footnote}% + \if@twocolumn + \ifnum \col@number=\@ne + \@maketitle + \else + \twocolumn[\@maketitle]% + \fi + \else + \newpage + \global\@topnum\z@ % Prevents figures from going at top of page. + \@maketitle + \fi + \thispagestyle{empty}\@thanks +% + \def\\{\unskip\ \ignorespaces}\def\inst##1{\unskip{}}% + \def\thanks##1{\unskip{}}\def\fnmsep{\unskip}% + \instindent=\hsize + \advance\instindent by-\headlineindent + \if!\the\toctitle!\addcontentsline{toc}{title}{\@title}\else + \addcontentsline{toc}{title}{\the\toctitle}\fi + \if@runhead + \if!\the\titlerunning!\else + \edef\@title{\the\titlerunning}% + \fi + \global\setbox\titrun=\hbox{\small\rm\unboldmath\ignorespaces\@title}% + \ifdim\wd\titrun>\instindent + \typeout{Title too long for running head. Please supply}% + \typeout{a shorter form with \string\titlerunning\space prior to + \string\maketitle}% + \global\setbox\titrun=\hbox{\small\rm + Title Suppressed Due to Excessive Length}% + \fi + \xdef\@title{\copy\titrun}% + \fi +% + \if!\the\tocauthor!\relax + {\def\and{\noexpand\protect\noexpand\and}% + \protected@xdef\toc@uthor{\@author}}% + \else + \def\\{\noexpand\protect\noexpand\newline}% + \protected@xdef\scratch{\the\tocauthor}% + \protected@xdef\toc@uthor{\scratch}% + \fi + \addtocontents{toc}{\noexpand\protect\noexpand\authcount{\the\c@auco}}% + \addcontentsline{toc}{author}{\toc@uthor}% + \if@runhead + \if!\the\authorrunning! + \value{@inst}=\value{@auth}% + \setcounter{@auth}{1}% + \else + \edef\@author{\the\authorrunning}% + \fi + \global\setbox\authrun=\hbox{\small\unboldmath\@author\unskip}% + \ifdim\wd\authrun>\instindent + \typeout{Names of authors too long for running head. Please supply}% + \typeout{a shorter form with \string\authorrunning\space prior to + \string\maketitle}% + \global\setbox\authrun=\hbox{\small\rm + Authors Suppressed Due to Excessive Length}% + \fi + \xdef\@author{\copy\authrun}% + \markboth{\@author}{\@title}% + \fi + \endgroup + \setcounter{footnote}{\fnnstart}% + \clearheadinfo} +% +\def\@maketitle{\newpage + \markboth{}{}% + \def\lastand{\ifnum\value{@inst}=2\relax + \unskip{} \andname\ + \else + \unskip \lastandname\ + \fi}% + \def\and{\stepcounter{@auth}\relax + \ifnum\value{@auth}=\value{@inst}% + \lastand + \else + \unskip, + \fi}% + \begin{center}% + \let\newline\\ + {\Large \bfseries\boldmath + \pretolerance=10000 + \@title \par}\vskip .8cm +\if!\@subtitle!\else {\large \bfseries\boldmath + \vskip -.65cm + \pretolerance=10000 + \@subtitle \par}\vskip .8cm\fi + \setbox0=\vbox{\setcounter{@auth}{1}\def\and{\stepcounter{@auth}}% + \def\thanks##1{}\@author}% + \global\value{@inst}=\value{@auth}% + \global\value{auco}=\value{@auth}% + \setcounter{@auth}{1}% +{\lineskip .5em +\noindent\ignorespaces +\@author\vskip.35cm} + {\small\institutename} + \end{center}% + } + +% definition of the "\spnewtheorem" command. +% +% Usage: +% +% \spnewtheorem{env_nam}{caption}[within]{cap_font}{body_font} +% or \spnewtheorem{env_nam}[numbered_like]{caption}{cap_font}{body_font} +% or \spnewtheorem*{env_nam}{caption}{cap_font}{body_font} +% +% New is "cap_font" and "body_font". It stands for +% fontdefinition of the caption and the text itself. +% +% "\spnewtheorem*" gives a theorem without number. +% +% A defined spnewthoerem environment is used as described +% by Lamport. +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def\@thmcountersep{} +\def\@thmcounterend{.} + +\def\spnewtheorem{\@ifstar{\@sthm}{\@Sthm}} + +% definition of \spnewtheorem with number + +\def\@spnthm#1#2{% + \@ifnextchar[{\@spxnthm{#1}{#2}}{\@spynthm{#1}{#2}}} +\def\@Sthm#1{\@ifnextchar[{\@spothm{#1}}{\@spnthm{#1}}} + +\def\@spxnthm#1#2[#3]#4#5{\expandafter\@ifdefinable\csname #1\endcsname + {\@definecounter{#1}\@addtoreset{#1}{#3}% + \expandafter\xdef\csname the#1\endcsname{\expandafter\noexpand + \csname the#3\endcsname \noexpand\@thmcountersep \@thmcounter{#1}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#4}{#5}}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@spynthm#1#2#3#4{\expandafter\@ifdefinable\csname #1\endcsname + {\@definecounter{#1}% + \expandafter\xdef\csname the#1\endcsname{\@thmcounter{#1}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#3}{#4}}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@spothm#1[#2]#3#4#5{% + \@ifundefined{c@#2}{\@latexerr{No theorem environment `#2' defined}\@eha}% + {\expandafter\@ifdefinable\csname #1\endcsname + {\newaliascnt{#1}{#2}% + \expandafter\xdef\csname #1name\endcsname{#3}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#4}{#5}}% + \global\@namedef{end#1}{\@endtheorem}}}} + +\def\@spthm#1#2#3#4{\topsep 7\p@ \@plus2\p@ \@minus4\p@ +\refstepcounter{#1}% +\@ifnextchar[{\@spythm{#1}{#2}{#3}{#4}}{\@spxthm{#1}{#2}{#3}{#4}}} + +\def\@spxthm#1#2#3#4{\@spbegintheorem{#2}{\csname the#1\endcsname}{#3}{#4}% + \ignorespaces} + +\def\@spythm#1#2#3#4[#5]{\@spopargbegintheorem{#2}{\csname + the#1\endcsname}{#5}{#3}{#4}\ignorespaces} + +\def\@spbegintheorem#1#2#3#4{\trivlist + \item[\hskip\labelsep{#3#1\ #2\@thmcounterend}]#4} + +\def\@spopargbegintheorem#1#2#3#4#5{\trivlist + \item[\hskip\labelsep{#4#1\ #2}]{#4(#3)\@thmcounterend\ }#5} + +% definition of \spnewtheorem* without number + +\def\@sthm#1#2{\@Ynthm{#1}{#2}} + +\def\@Ynthm#1#2#3#4{\expandafter\@ifdefinable\csname #1\endcsname + {\global\@namedef{#1}{\@Thm{\csname #1name\endcsname}{#3}{#4}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@Thm#1#2#3{\topsep 7\p@ \@plus2\p@ \@minus4\p@ +\@ifnextchar[{\@Ythm{#1}{#2}{#3}}{\@Xthm{#1}{#2}{#3}}} + +\def\@Xthm#1#2#3{\@Begintheorem{#1}{#2}{#3}\ignorespaces} + +\def\@Ythm#1#2#3[#4]{\@Opargbegintheorem{#1} + {#4}{#2}{#3}\ignorespaces} + +\def\@Begintheorem#1#2#3{#3\trivlist + \item[\hskip\labelsep{#2#1\@thmcounterend}]} + +\def\@Opargbegintheorem#1#2#3#4{#4\trivlist + \item[\hskip\labelsep{#3#1}]{#3(#2)\@thmcounterend\ }} + +\if@envcntsect + \def\@thmcountersep{.} + \spnewtheorem{theorem}{Theorem}[section]{\bfseries}{\itshape} +\else + \spnewtheorem{theorem}{Theorem}{\bfseries}{\itshape} + \if@envcntreset + \@addtoreset{theorem}{section} + \else + \@addtoreset{theorem}{chapter} + \fi +\fi + +%definition of divers theorem environments +\spnewtheorem*{claim}{Claim}{\itshape}{\rmfamily} +\spnewtheorem*{proof}{Proof}{\itshape}{\rmfamily} +\if@envcntsame % alle Umgebungen wie Theorem. + \def\spn@wtheorem#1#2#3#4{\@spothm{#1}[theorem]{#2}{#3}{#4}} +\else % alle Umgebungen mit eigenem Zaehler + \if@envcntsect % mit section numeriert + \def\spn@wtheorem#1#2#3#4{\@spxnthm{#1}{#2}[section]{#3}{#4}} + \else % nicht mit section numeriert + \if@envcntreset + \def\spn@wtheorem#1#2#3#4{\@spynthm{#1}{#2}{#3}{#4} + \@addtoreset{#1}{section}} + \else + \def\spn@wtheorem#1#2#3#4{\@spynthm{#1}{#2}{#3}{#4} + \@addtoreset{#1}{chapter}}% + \fi + \fi +\fi +\spn@wtheorem{case}{Case}{\itshape}{\rmfamily} +\spn@wtheorem{conjecture}{Conjecture}{\itshape}{\rmfamily} +\spn@wtheorem{corollary}{Corollary}{\bfseries}{\itshape} +\spn@wtheorem{definition}{Definition}{\bfseries}{\itshape} +\spn@wtheorem{example}{Example}{\itshape}{\rmfamily} +\spn@wtheorem{exercise}{Exercise}{\itshape}{\rmfamily} +\spn@wtheorem{lemma}{Lemma}{\bfseries}{\itshape} +\spn@wtheorem{note}{Note}{\itshape}{\rmfamily} +\spn@wtheorem{problem}{Problem}{\itshape}{\rmfamily} +\spn@wtheorem{property}{Property}{\itshape}{\rmfamily} +\spn@wtheorem{proposition}{Proposition}{\bfseries}{\itshape} +\spn@wtheorem{question}{Question}{\itshape}{\rmfamily} +\spn@wtheorem{solution}{Solution}{\itshape}{\rmfamily} +\spn@wtheorem{remark}{Remark}{\itshape}{\rmfamily} + +\def\@takefromreset#1#2{% + \def\@tempa{#1}% + \let\@tempd\@elt + \def\@elt##1{% + \def\@tempb{##1}% + \ifx\@tempa\@tempb\else + \@addtoreset{##1}{#2}% + \fi}% + \expandafter\expandafter\let\expandafter\@tempc\csname cl@#2\endcsname + \expandafter\def\csname cl@#2\endcsname{}% + \@tempc + \let\@elt\@tempd} + +\def\theopargself{\def\@spopargbegintheorem##1##2##3##4##5{\trivlist + \item[\hskip\labelsep{##4##1\ ##2}]{##4##3\@thmcounterend\ }##5} + \def\@Opargbegintheorem##1##2##3##4{##4\trivlist + \item[\hskip\labelsep{##3##1}]{##3##2\@thmcounterend\ }} + } + +\renewenvironment{abstract}{% + \list{}{\advance\topsep by0.35cm\relax\small + \leftmargin=1cm + \labelwidth=\z@ + \listparindent=\z@ + \itemindent\listparindent + \rightmargin\leftmargin}\item[\hskip\labelsep + \bfseries\abstractname]} + {\endlist} + +\newdimen\headlineindent % dimension for space between +\headlineindent=1.166cm % number and text of headings. + +\def\ps@headings{\let\@mkboth\@gobbletwo + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\normalfont\small\rlap{\thepage}\hspace{\headlineindent}% + \leftmark\hfil} + \def\@oddhead{\normalfont\small\hfil\rightmark\hspace{\headlineindent}% + \llap{\thepage}} + \def\chaptermark##1{}% + \def\sectionmark##1{}% + \def\subsectionmark##1{}} + +\def\ps@titlepage{\let\@mkboth\@gobbletwo + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\normalfont\small\rlap{\thepage}\hspace{\headlineindent}% + \hfil} + \def\@oddhead{\normalfont\small\hfil\hspace{\headlineindent}% + \llap{\thepage}} + \def\chaptermark##1{}% + \def\sectionmark##1{}% + \def\subsectionmark##1{}} + +\if@runhead\ps@headings\else +\ps@empty\fi + +\setlength\arraycolsep{1.4\p@} +\setlength\tabcolsep{1.4\p@} + +\endinput +%end of file llncs.cls diff --git a/docs/posters/poster.pdf b/docs/posters/poster.pdf new file mode 100644 index 0000000000..fd4d86cfca Binary files /dev/null and b/docs/posters/poster.pdf differ diff --git a/docs/posters/poster.tex b/docs/posters/poster.tex new file mode 100644 index 0000000000..3746788c2f --- /dev/null +++ b/docs/posters/poster.tex @@ -0,0 +1,62 @@ +\documentclass[article]{llncs} + +\newcommand\TOCwithBibliography[2][plain]{% + \begingroup + \let\clearpage\relax + \bibliographystyle{#1} + \bibliography{#2} + \endgroup + \clearpage +} + +\begin{document} + +\title{ErgoScript: A More Expressive Alternative to Bitcoin Script} +\author{Ergo Developers} +\institute{\email{\{ergoplatform\}@protonmail.com}} + +\maketitle + +Every coin in Bitcoin is protected by a program in the stack-based Bitcoin Script +language. An interpreter for the language is evaluating the program against a redeeming +program~(in the same language) as well as a context (few variables information about +the state of the blockchain system). The evaluation produces a single boolean value as +a result. While Bitcoin Script allows for some contracts to be programmed, its +abilities are limited since many instructions were removed after denial-of-service and +security issues were discovered. To add new cryptographic primitives to the language +(such as ring signatures), a hard-fork is required. + +Generalizing and overcoming the limitations of Bitcoin Script, we introduce a notion of +an \emph{authentication language} where a transaction verifier is running an +interpreter with three inputs: 1) a \textit{proposition} defined in terms of the +language; 2) a \textit{context} and 3) a \textit{proof}~(defined in the \emph{language +of proofs}) generated by a prover for the proposition against the same context. The +interpreter is producing a boolean value and must finish for any possible +inputs within constant time. + +We designed ErgoScript as a call-by-value, higher-order functional language without +recursion with widely known concise Scala/Kotlin syntax. It supports single-assignment +blocks, tuples, optional values, indexed collections with higher-order operations, +short-cutting logicals, ternary 'if' with lazy branches. All +operations are deterministic, without side effects and all values are immutable. +ErgoScript in not turing-complete, however it is expressive enough to make the whole +transactional model of Ergo turing complete. + +ErgoScript defines a guarding proposition for a coin as a logic formula which combines +predicates over a context and cryptographic statements provable via $\Sigma$-protocols +with AND, OR, k-out-of-n connectives. A user willing to spend the coin first evaluates +the proposition over known context and entire spending transaction yielding a +\emph{$\Sigma$-protocol statement}. Then the prover is turning the statement into a +signature with the help of a Fiat-Shamir transformation. A transaction verifier~(a +full-node in a blockchain setting) evaluates the proposition against the context and +checks the signature. Language expressiveness is defined by a set of predicates over +context and a set of $\Sigma$-protocol statements. We show how the latter can be +extended with a soft-fork by using versioning conventions. We propose a set of context +predicates for a Bitcoin-like cryptocurrency with a guarantee of constant verification +time. We provide several examples: zero knowledge ring and threshold signatures, +pre-issued mining rewards, crowd-funding, demurrage currency, DEX, LETS, ICO, +non-interactive CoinJoin, etc. + +\TOCwithBibliography{sources} + +\end{document} \ No newline at end of file diff --git a/docs/posters/sources.bib b/docs/posters/sources.bib new file mode 100644 index 0000000000..acaf5838b9 --- /dev/null +++ b/docs/posters/sources.bib @@ -0,0 +1,442 @@ +@Inbook{Garay2015, +author="Garay, Juan +and Kiayias, Aggelos +and Leonardos, Nikos", +editor="Oswald, Elisabeth +and Fischlin, Marc", +title="The Bitcoin Backbone Protocol: Analysis and Applications", +bookTitle="Advances in Cryptology - EUROCRYPT 2015: 34th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Sofia, Bulgaria, April 26-30, 2015, Proceedings, Part II", +year="2015", +publisher="Springer Berlin Heidelberg", +address="Berlin, Heidelberg", +pages="281--310", +isbn="978-3-662-46803-6", +doi="10.1007/978-3-662-46803-6_10", +url="http://dx.doi.org/10.1007/978-3-662-46803-6_10" +} +@inproceedings{meiklejohn2010zkpdl, + title={ZKPDL: A Language-Based System for Efficient Zero-Knowledge Proofs and Electronic Cash.}, + author={Meiklejohn et al., Sarah}, + booktitle={USENIX Security Symposium}, + volume={10}, + pages={193--206}, + year={2010} +} +@inproceedings{groth2006perfect, + title={Perfect non-interactive zero knowledge for NP}, + author={Groth, Jens and Ostrovsky, Rafail and Sahai, Amit}, + booktitle={Annual International Conference on the Theory and Applications of Cryptographic Techniques}, + pages={339--358}, + year={2006}, + organization={Springer} +} +@inproceedings{fiat1986prove, + title={How to prove yourself: Practical solutions to identification and signature problems}, + author={Fiat, Amos and Shamir, Adi}, + booktitle={Conference on the Theory and Application of Cryptographic Techniques}, + pages={186--194}, + year={1986}, + organization={Springer} +} +@incollection{ben2013snarks, + title={SNARKs for C: Verifying program executions succinctly and in zero knowledge}, + author={Ben-Sasson, Eli and Chiesa, Alessandro and Genkin, Daniel and Tromer, Eran and Virza, Madars}, + booktitle={Advances in Cryptology--CRYPTO 2013}, + pages={90--108}, + year={2013}, + publisher={Springer} +} +@misc{cryptoeprint:2013:879, + author = {Eli Ben-Sasson and Alessandro Chiesa and Eran Tromer and Madars Virza}, + title = {Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture}, + howpublished = {Cryptology ePrint Archive, Report 2013/879}, + year = {2013}, +} +@incollection{damgaard1999commitment, + title={Commitment schemes and zero-knowledge protocols}, + author={Damg{\aa}rd, Ivan}, + booktitle={Lectures on Data Security}, + pages={63--86}, + year={1999}, + publisher={Springer} +} +@inproceedings{boneh2003aggregate, + title={Aggregate and verifiably encrypted signatures from bilinear maps}, + author={Boneh, Dan and Gentry, Craig and Lynn, Ben and Shacham, Hovav}, + booktitle={International Conference on the Theory and Applications of Cryptographic Techniques}, + pages={416--432}, + year={2003}, + organization={Springer} +} +@inproceedings{saxena2014increasing, + title={Increasing anonymity in bitcoin}, + author={Saxena, Amitabh and Misra, Janardan and Dhar, Aritra}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={122--139}, + year={2014}, + organization={Springer} +} +@inproceedings{miers2013zerocoin, + title={Zerocoin: Anonymous distributed e-cash from bitcoin}, + author={Miers, Ian and Garman, Christina and Green, Matthew and Rubin, Aviel D}, + booktitle={Security and Privacy (SP), 2013 IEEE Symposium on}, + pages={397--411}, + year={2013}, + organization={IEEE} +} +@inproceedings{sasson2014zerocash, + title={Zerocash: Decentralized anonymous payments from bitcoin}, + author={Sasson, Eli Ben and Chiesa, Alessandro and Garman, Christina and Green, Matthew and Miers, Ian and Tromer, Eran and Virza, Madars}, + booktitle={2014 IEEE Symposium on Security and Privacy}, + pages={459--474}, + year={2014}, + organization={IEEE} +} +@inproceedings{eyal2016bitcoin, + title={Bitcoin-NG: A scalable blockchain protocol}, + author={Eyal, Ittay and Gencer, Adem Efe and Sirer, Emin G{\"u}n and Van Renesse, Robbert}, + booktitle={13th USENIX Symposium on Networked Systems Design and Implementation (NSDI 16)}, + pages={45--59}, + year={2016} +} +@misc{cryptoeprint:2013:881, + author = {Yonatan Sompolinsky and Aviv Zohar}, + title = {Accelerating Bitcoin's Transaction Processing. Fast Money Grows on Trees, Not Chains}, + howpublished = {Cryptology ePrint Archive, Report 2013/881}, + year = {2013}, +} +@article{DBLP:journals/corr/ChepurnoyLO16, + author = {Alexander Chepurnoy and + Mario Larangeira and + Alexander Ojiganov}, + title = {A Prunable Blockchain Consensus Protocol Based on Non-Interactive + Proofs of Past States Retrievability}, + journal = {CoRR}, + volume = {abs/1603.07926}, + year = {2016}, + url = {http://arxiv.org/abs/1603.07926}, + timestamp = {Sat, 02 Apr 2016 11:49:48 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/corr/ChepurnoyLO16}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@misc{stewart2012proof, + title={Proof of burn. bitcoin. i t}, + author={Stewart, I}, + year={2012}, + publisher={December} +} +@article{king2012ppcoin, + title={Ppcoin: Peer-to-peer crypto-currency with proof-of-stake}, + author={King, Sunny and Nadal, Scott}, + journal={self-published paper, August}, + volume={19}, + year={2012} +} +@misc{kiayias2016provably, + title={A Provably Secure Proof-of-Stake Blockchain Protocol}, + author={Kiayias, Aggelos and Konstantinou, Ioannis and Russell, Alexander and David, Bernardo and Oliynykov, Roman}, + year={2016} +} +@inproceedings{douceur2002sybil, + title={The sybil attack}, + author={Douceur, John R}, + booktitle={International Workshop on Peer-to-Peer Systems}, + pages={251--260}, + year={2002}, + organization={Springer} +} +@misc{back2002hashcash, + title={Hashcash-a denial of service counter-measure}, + author={Back, Adam and others}, + year={2002} +} + +@misc{script, + title={Script}, + author={Bitcoin Wiki}, + url={https://en.bitcoin.it/wiki/Script} +} + +@inproceedings{decker2014bitcoin, + title={Bitcoin transaction malleability and MtGox}, + author={Decker, Christian and Wattenhofer, Roger}, + booktitle={European Symposium on Research in Computer Security}, + pages={313--326}, + year={2014}, + organization={Springer} +} + + +@article{Beekman2016, +abstract = {Bitcoin supports complex transactions where the recipient of a transaction can be programmatically determined. Using these transactions, multi-party computation protocols that aim to ensure fairness among participants have been designed. We present a Denial of Service attack against these protocols that results in a net loss for some or all of the honest parties involved, violating those fairness goals.}, +author = {Beekman, Jethro G.}, +doi = {10.1016/j.ipl.2015.09.009}, +issn = {00200190}, +journal = {Information Processing Letters}, +keywords = {Bitcoin,Cryptography,Denial of Service,Safety/security in digital systems,Secure multiparty computation,bitcoin,smc,smp}, +mendeley-tags = {bitcoin,smc,smp}, +number = {2}, +pages = {144--146}, +title = {{A Denial of Service attack against fair computations using Bitcoin deposits}}, +url = {https://eprint.iacr.org/2014/911}, +volume = {116}, +year = {2016} +} + +@article{Bentov2013, + title={Proof of Activity: Extending Bitcoin's Proof of Work via Proof of Stake}, + author={Bentov, Iddo and Lee, Charles and Mizrahi, Alex and Rosenfeld, Meni}, + journal={ACM SIGMETRICS Performance Evaluation Review}, + volume={42}, + number={3}, + pages={34--37}, + year={2014}, + publisher={ACM} +} +@misc{Bonneau2015, +abstract = {Bitcoin has emerged as the most successful cryptographic currency in history. Within two years of its quiet launch in 2009, Bitcoin grew to comprise billions of dollars of economic value despite only cursory analysis of the system’s design. Since then a growing literature has identified hidden-but-important properties of the system, discovered attacks, proposed promising alternatives, and singled out difficult future challenges. Meanwhile a large and vibrant open-source community has proposed and deployed numerous modifications and extensions. We provide the first systematic exposition Bitcoin and the many related cryptocurrencies or ‘altcoins.’ Drawing from a scattered body of knowledge, we identify three key components of Bitcoin’s design that can be decoupled. This enables a more insightful analysis of Bitcoin’s properties and future stability. We map the design space for numerous proposed modifications, providing comparative analyses for alternative consensus mechanisms, currency allocation mechanisms, computational puzzles, and key management tools. We survey anonymity issues in Bitcoin and provide an evaluation framework for analyzing a variety of privacy-enhancing proposals. Finally we provide new insights on what we term disintermediation protocols, which absolve the need for trusted intermediaries in an interesting set of applications. We identify three general disintermediation strategies and provide a detailed comparison.}, +author = {Bonneau, Joseph and Miller, Andrew and Clark, Jeremy and Narayanan, Arvind and Kroll, Joshua A. and Felten, Edward W.}, +booktitle = {Proceedings - IEEE Symposium on Security and Privacy}, +doi = {10.1109/SP.2015.14}, +isbn = {9781467369497}, +issn = {10816011}, +keywords = {bitcoin,cryptocurrency}, +mendeley-tags = {bitcoin,cryptocurrency}, +pages = {104--121}, +title = {{SoK: Research perspectives and challenges for bitcoin and cryptocurrencies}}, +url = {http://www.ieee-security.org/TC/SP2015/papers-archived/6949a104.pdf}, +urldate = {2015-12-21}, +volume = {2015-July}, +year = {2015} +} +@article{biryukov2016asymmetric, + title={Asymmetric proof-of-work based on the Generalized Birthday problem}, + author={Biryukov, Alex and Khovratovich, Dmitry}, + journal={Proceedings of NDSS 2016}, + pages={13}, + year={2016} +} +@inproceedings{miller2015nonoutsourceable, + title={Nonoutsourceable Scratch-Off Puzzles to Discourage Bitcoin Mining Coalitions}, + author={Miller, Andrew and Kosba, Ahmed and Katz, Jonathan and Shi, Elaine}, + booktitle={Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security}, + pages={680--691}, + year={2015}, + organization={ACM} +} +@misc{cromanscaling, + title={On Scaling Decentralized Blockchains}, + author={Croman, Kyle and Decker, Christian and Eyal, Ittay and Gencer, Adem Efe and Juels, Ari and Kosba, Ahmed and Miller, Andrew and Saxena, Prateek and Shi, Elaine and G{\"u}n, Emin}, + url = {http://fc16.ifca.ai/bitcoin/papers/CDE+16.pdf} +} +@misc{Chepurnoy, +author = {Chepurnoy, Alexander}, +title = {{Interactive Proof-of-Stake simulation tools}}, +url = {https://github.com/kushti/common-pos}, +urldate = {2015-12-31} +} +@misc{bitcoind, +title = {{Bitcoin Core Code Repository}}, +url = {https://github.com/bitcoin/bitcoin/}, +urldate = {2016-03-24} +} +@misc{ethyp, +title = {{Ethereum: A Secure Decentralized Generalized Transaction Ledger}}, +url = {http://gavwood.com/Paper.pdf}, +urldate = {2016-03-24} +} +@misc{cryptonite, +title = {{The Cryptonite Project Homepage}}, +url = {http://cryptonite.info}, +urldate = {2016-03-24} +} +@misc{snapshot, +title = {{Bitcoin Blockchain Data Torrent}}, +url = {https://bitcointalk.org/index.php?topic=145386.0}, +urldate = {2016-03-24} +} +@misc{minibc, +title = {{The Mini-Blockchain Scheme}}, +url = {http://cryptonite.info/files/mbc-scheme-rev2.pdf}, +urldate = {2016-03-24} +} +@article{Eyal2014, +abstract = {The Bitcoin cryptocurrency records its transactions in a public log called the blockchain. Its security rests critically on the distributed protocol that maintains the blockchain, run by participants called miners. Conventional wisdom asserts that the protocol is incentive-compatible and secure against colluding minority groups, i.e., it incentivizes miners to follow the protocol as prescribed. We show that the Bitcoin protocol is not incentive-compatible. We present an attack with which colluding miners obtain a revenue larger than their fair share. This attack can have significant consequences for Bitcoin: Rational miners will prefer to join the selfish miners, and the colluding group will increase in size until it becomes a majority. At this point, the Bitcoin system ceases to be a decentralized currency. Selfish mining is feasible for any group size of colluding miners. We pro- pose a practical modification to the Bitcoin protocol that protects against selfish mining pools that command less than 1/4 of the resources. This threshold is lower than the wrongly assumed 1/2 bound, but better than the current reality where a group of any size can compromise the system.}, +archivePrefix = {arXiv}, +arxivId = {1311.0243}, +author = {Eyal, Ittay and Sirer, Emin G??n}, +doi = {10.1007/978-3-662-45472-5{\_}28}, +eprint = {1311.0243}, +file = {:home/kushti/.local/share/data/Mendeley Ltd./Mendeley Desktop/Downloaded/Eyal, Sirer - 2013 - Majority is not Enough Bitcoin Mining is Vulnerable.pdf:pdf}, +isbn = {9783662454718}, +issn = {16113349}, +journal = {Lecture Notes in Computer Science (including subseries Lecture Notes in Artificial Intelligence and Lecture Notes in Bioinformatics)}, +keywords = {bitcoin,proof-of-work}, +mendeley-tags = {bitcoin,proof-of-work}, +month = {nov}, +number = {Jan}, +pages = {436--454}, +title = {{Majority is not enough: Bitcoin mining is vulnerable}}, +url = {http://arxiv.org/pdf/1311.0243v1.pdf}, +volume = {8437}, +year = {2014} +} +@incollection{garay2015bitcoin, + title={The bitcoin backbone protocol: Analysis and applications}, + author={Garay, Juan and Kiayias, Aggelos and Leonardos, Nikos}, + booktitle={Advances in Cryptology-EUROCRYPT 2015}, + pages={281--310}, + year={2015}, + publisher={Springer} +} + +@inproceedings{luu2015demystifying, + title={Demystifying incentives in the consensus computer}, + author={Luu, Loi and Teutsch, Jason and Kulkarni, Raghav and Saxena, Prateek}, + booktitle={Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security}, + pages={706--719}, + year={2015}, + organization={ACM} +} +@inproceedings{merkle1987digital, + title={A digital signature based on a conventional encryption function}, + author={Merkle, Ralph C}, + booktitle={Advances in Cryptology—CRYPTO’87}, + pages={369--378}, + year={1987}, + organization={Springer} +} +@misc{Maxwell2015, +author = {Maxwell, Greg}, +keywords = {Pedersen commitment,anonymous transactions,commitment,cryptography,zero knowledge}, +mendeley-tags = {Pedersen commitment,anonymous transactions,commitment,cryptography,zero knowledge}, +title = {{Confidential Transactions}}, +url = {https://people.xiph.org/{~}greg/confidential{\_}values.txt}, +urldate = {2015-09-10}, +year = {2015} +} +@misc{milleranonymous, + title={Anonymous Byzantine Consensus from Moderately-Hard Puzzles: A Model for Bitcoin}, + author={Miller, Andrew and LaViola Jr, Joseph J}, + url = {https://socrates1024.s3.amazonaws.com/consensus.pdf} +} +@inproceedings{miller2014permacoin, + title={Permacoin: Repurposing bitcoin work for data preservation}, + author={Miller, Andrew and Juels, Ari and Shi, Elaine and Parno, Bryan and Katz, Jonathan}, + booktitle={Security and Privacy (SP), 2014 IEEE Symposium on}, + pages={475--490}, + year={2014}, + organization={IEEE} +} +@article{Moran, +abstract = {We introduce a new cryptographic primitive: Proofs of Space-Time (PoSTs) and construct a practical protocol for implementing these proofs. A PoST allows a prover to convince a verifier that she spent a ``spacetime'' resource (storing data---space---over a period of time). +Formally, we define the PoST resource as a linear tradeoff between CPU work and space-time (under reasonable cost assumptions, a rational user will prefer to use the lower-cost space-time resource over CPU work). + +Compared to a proof-of-work, a PoST requires less energy use, as the ``difficulty'' can be increased by extending the time period over which data is stored without increasing computation costs. +Our definition is very similar to ``Proofs of Space'' [ePrint 2013/796, 2013/805] but, unlike the previous definitions, takes into account amortization attacks and storage duration. Moreover, our protocol uses a very different (and simpler) technique, making use of the fact that we explicitly allow a space-time tradeoff.}, +author = {Moran, Tal and Orlov, Ilan}, +file = {:home/kushti/.local/share/data/Mendeley Ltd./Mendeley Desktop/Downloaded/Moran, Orlov - Unknown - Proofs of Space-Time and Rational Proofs of Storage.pdf:pdf}, +keywords = {Proofs of Space,Proofs of Space-Time,Proofs of Storage,bitcoin,crypto-currency,proofs of space,proofs of work}, +mendeley-tags = {Proofs of Space,Proofs of Space-Time,Proofs of Storage}, +title = {{Proofs of Space-Time and Rational Proofs of Storage}}, +url = {http://eprint.iacr.org/2016/035} +} +@misc{Nakamoto2008, +abstract = {A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending. We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone.}, +archivePrefix = {arXiv}, +arxivId = {43543534534v343453}, +author = {Nakamoto, Satoshi}, +doi = {10.1007/s10838-008-9062-0}, +eprint = {43543534534v343453}, +isbn = {978-972-757-716-3}, +issn = {09254560}, +keywords = {bitcoin,whitepaper}, +mendeley-tags = {bitcoin,whitepaper}, +pages = {1--9}, +pmid = {14533183}, +title = {{Bitcoin: A Peer-to-Peer Electronic Cash System}}, +url = {https://bitcoin.org/bitcoin.pdf}, +urldate = {2016-03-24}, +year = {2008} +} +@misc{Park2015, +abstract = {We propose a decentralized cryptocurrency based on a block-chain ledger similar to that of Bitcoin, but where the extremely wasteful proofs of work are replaced by proofs of space, recently introduced by Dziembowski et al. (CRYPTO 2015). Instead of requiring that a majority of the computing power is controlled by honest miners (as in Bitcoin), our currency requires that honest miners dedicate more disk space than a potential adversary.$\backslash$r$\backslash$rOnce a miner has dedicated and initialized some space, participating in the mining process is very cheap. A new block is added to the chain every fixed period of time (say, every minute), and in every period a miner just has to make a small number of lookups to the stored space to check if she ``wins", and thus can add the next block to the chain and get the mining reward. Because this check is cheap, proof-of-space-based currencies share some (but not all) issues with currencies based on ``proofs of stake'', like Peercoin. Concretely, a na$\backslash$"ive solution that simply replaces proofs of work with proofs of space raises two main issues which we address:$\backslash$r$\backslash$r$\backslash$emph{\{}Grinding:{\}} A miner who can add the next block has some degree of freedom in shaping how the chain looks, e.g. by trying out different sets of transactions to include in her block. The miner can try many possible choices until she finds one which results in a chain that allows her to also mine the next block, thus hijacking the chain forever while dedicating only a small amount of the space. We solve this problem fully by ``decoupling" the hash chain from the transactions, so that there is nothing to grind. To bind the transactions back to the hash chain, we add an extra signature chain, which guarantees that past transactions cannot be altered once an honest miner adds a block. Our solution also gives a simple and novel way to solve the grinding problem in currencies based on proofs of stake.$\backslash$r$\backslash$r$\backslash$emph{\{}Mining multiple chains:{\}} Since checking whether one can add a block is cheap, rational miners will not only try to extend the so-far-best chain, but also try other chains, in the hope that they can extend one of them which will ultimately catch up and overtake the currently-best chain. (In the context of proof-of-stake-based currencies this is known as the ``nothing-at-stake" problem.) This not only gives rational miners a larger-than-expected reward (compared to what honest miners get), but also makes consensus very slow, if not impossible. Our solution to this problem is based on penalizing miners who try to work on more than one branch of the chain.}, +author = {Park, Sunoo and Pietrzak, Krzysztof and Alwen, Joel and Fuchsbauer, Georg and Gazi, Peter}, +booktitle = {IACR Cryptology ePrint Archive}, +keywords = {consensus algo,proof-of-space,spacecoin}, +mendeley-tags = {consensus algo,proof-of-space,spacecoin}, +pages = {1--26}, +title = {{Spacecoin : A Cryptocurrency Based on Proofs of Space}}, +url = {http://eprint.iacr.org/2015/528.pdf}, +urldate = {2015-10-03}, +year = {2015} +} +@article{Sengupta, +author = {Sengupta, Binanda and Bag, Samiran and Ruj, Sushmita and Sakurai, Kouichi}, +file = {:home/kushti/Downloads/Retricoin.pdf:pdf}, +isbn = {9781450340328}, +keywords = {bilinear pairings,bitcoin,mining,proofs of retrievability}, +title = {{Retricoin: Bitcoin Based on Compact Proofs of Retrievability}} +} +@incollection{douceur2002sybil, + title={The sybil attack}, + author={Douceur, John R}, + booktitle={Peer-to-peer Systems}, + pages={251--260}, + year={2002}, + publisher={Springer} +} +@inproceedings{heilman2015eclipse, + title={Eclipse attacks on Bitcoin’s peer-to-peer network}, + author={Heilman, Ethan and Kendler, Alison and Zohar, Aviv and Goldberg, Sharon}, + booktitle={24th USENIX Security Symposium (USENIX Security 15)}, + pages={129--144}, + year={2015} +} +@book{katz2014introduction, + title={Introduction to modern cryptography}, + author={Katz, Jonathan and Lindell, Yehuda}, + year={2014}, + publisher={CRC press} +} +@book{antonopoulos2014mastering, + title={Mastering Bitcoin: unlocking digital cryptocurrencies}, + author={Antonopoulos, Andreas M}, + year={2014}, + publisher={" O'Reilly Media, Inc."} +} +@misc{nielsen, + title={How the Bitcoin protocol actually works}, + url={http://www.michaelnielsen.org/ddi/how-the-bitcoin-protocol-actually-works/}, + journal={DDI}, + author={Nielsen, Michael} +} +@article{fischer1985impossibility, + title={Impossibility of distributed consensus with one faulty process}, + author={Fischer, Michael J and Lynch, Nancy A and Paterson, Michael S}, + journal={Journal of the ACM (JACM)}, + volume={32}, + number={2}, + pages={374--382}, + year={1985}, + publisher={ACM} +} +@inproceedings{sompolinsky2015secure, + title={Secure high-rate transaction processing in Bitcoin}, + author={Sompolinsky, Yonatan and Zohar, Aviv}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={507--527}, + year={2015}, + organization={Springer} +} +@misc{stackexchange, + author={StackExchange}, + title={Strongest vs Longest chain and orphaned blocks}, + url={http://bitcoin.stackexchange.com/questions/29742/strongest-vs-longest-chain-and-orphaned-blocks} +} +@book{odersky2008programming, + title={Programming in scala}, + author={Odersky, Martin and Spoon, Lex and Venners, Bill}, + year={2008}, + publisher={Artima Inc} +} diff --git a/docs/wpaper/sigma.tex b/docs/wpaper/sigma.tex index 12b79d0e2b..cae84028b2 100644 --- a/docs/wpaper/sigma.tex +++ b/docs/wpaper/sigma.tex @@ -77,7 +77,7 @@ \section{Introduction} \subsection{Background} -Since its early days, Bitcoin~\cite{Nak08} has allowed more than simple money transfers between two public keys: its Bitcoin Script scripting language has allowed participants to specify conditions for how money could be spent. A program written in Bitcoin Script is attached to every transaction output (i.e., amount received); this program protects the transaction by determining how the transaction output can be used as an input to (i.e., spent in) a future transaction. The simplest condition is specified by a program that contains the recipient's public key and states that the money can be spent by creating a signature that verifies under this key. However, more general conditions are allowed by more sophisticated programs. +Since its early days, Bitcoin~\cite{Nak08} has allowed more than simple money transfers between two public keys: its Bitcoin Script scripting language has allowed participants to specify conditions for how money could be spent. A program written in Bitcoin Script is attached to every transaction output (i.e., amount received); this program protects the transaction by determining how the transaction output can be used as an input to (i.e., spent in) a future transaction. The simplest condition is specified by a program that contains the recipient's public key and states that the money can be spent by creating a signature that verifies under this key. However, more general conditions are allowed by more sophisticated programs. The Bitcoin Script language is a primitive stack-based language without loops~\cite{bscript}. To spend an output protected by a program, a spending transaction must provide a program in the same language, and the concatenation of the two programs must evaluate to \emph{true}. The creator of the spending transaction can be viewed as a prover (for example, proving knowledge of the secret key by producing a signature), where the statement that needs to be proven is specified by the output that is being spent. Transaction validity is verified by evaluating programs. Bounded validation time is ensured by the absence of loops in the programming language and a maximum program size of 10 kilobytes. Even so, some denial-of-service attacks exploiting script validation time have appeared \cite{bitcoindos, fivehrs, ethattacks}. On the other hand, the deliberate simplicity of the programming language limits the kinds of contracts that can be created on the Bitcoin platform. @@ -105,7 +105,7 @@ \subsection{Our Contribution: \langname} \langname also provides the ability to build more sophisticated $\Sigma$-protocols by using connectives $\andnode$, $\ornode$, and $\tnode$ (also known as $k$-out-of-$n$). -Crucially, the proof for an $\ornode$ and a $\tnode$ connective does not reveal which of the relevant values the prover knows: for example, in \langname a ring signature by public keys $h_1, \dots, h_n$ can be specified as an $\ornode$ of $\Sigma$-protocols for proving knowledge of discrete logarithms of $h_1, \dots, h_n$. The proof can be constructed with the knowledge of just one such discrete logarithm, and does not reveal which one was used in its construction. +Crucially, the proof for an $\ornode$ and a $\tnode$ connective does not reveal which of the relevant values the prover knows: for example, in \langname a ring signature by public keys $h_1, \dots, h_n$ can be specified as an $\ornode$ of $\Sigma$-protocols for proving knowledge of discrete logarithms of $h_1, \dots, h_n$. The proof can be constructed with the knowledge of just one such discrete logarithm, and does not reveal which one was used in its construction. Our implementation of these protocols is in Scala \cite{scala} and Java \cite{java}. The implementation was informed by SCAPI \cite{scapi}, but does not use SCAPI code. We use Bouncy Castle \cite{bouncycastle} for big integer and elliptic curve operations; the implementation of arithmetic in fields of characteristic 2 (for $\tnode$ connectives) is our own. @@ -126,7 +126,7 @@ \subsection{Our Contribution: \langname} \section{\langname Language Description} -The language syntax is a subset of Scala with the same meaning, and therefore many of the constructs are easy to read for those familiar with Scala. +The language syntax is a subset of Scala with the same meaning, and therefore many of the constructs are easy to read for those familiar with Scala. Before we describe the language, let us fix some terminology. A \emph{box} (often called a ``coin'' in other literature) contains some amount (\emph{value}, measured in Ergo tokens) and is protected by a \emph{script} (boxes also contain additional information, such as other tokens; this information is described in detail in Section \ref{sec:box-registers}). A \emph{transaction} spends the value of boxes that are its \emph{inputs} (which are outputs of some earlier transaction) and produces boxes that are its \emph{outputs}. In a given transaction, the sum of the values of the inputs must equal the sum of the values of the outputs (as we describe below, the scripting language is rich enough to allow for payment transactions fees and for minting of new coins without violating this rule). @@ -159,7 +159,7 @@ \subsection{$\Sigma$-Statements} \begin{verbatim} pkA || pkB || pkC \end{verbatim} - + For syntactic convenience, multiple keys can be placed into a collection, and \texttt{anyOf} operator can be used instead of \texttt{||}, as follows: \begin{verbatim} anyOf (Coll (pkA, pkB, pkC)) @@ -183,7 +183,7 @@ \subsection{$\Sigma$-Statements} Operators \texttt{allOf} and \texttt{anyOf} are actually special cases of the operator \texttt{atLeast(bound, collection)}, which requires the prover to know secrets for $\texttt{bound}$ elements of the \texttt{collection} (just in like in \texttt{anyOf}, which secrets the prover used will not be revealed by the proof). \texttt{allOf(collection)} is the same as \texttt{atLeast(collection.size, collection)} and $\texttt{anyOf(collection)}$ is the same as \texttt{atLeast(1, collection)}. -For example, here is a script stating that the box can be spent by any 3 out of the following 6 possibilities: Alice, Bob, Charlie, David and Edie, Fran and George, Helen and Irene: +For example, here is a script stating that the box can be spent by any 3 out of the following 6 possibilities: Alice, Bob, Charlie, David and Edie, Frank and George, Helen and Irene: \begin{verbatim} atLeast(3, Coll (pkA, pkB, pkC, pkD && pkE, pkF && pkG, pkH && pkI)) \end{verbatim} @@ -204,7 +204,7 @@ \subsection{Mixing $\Sigma$-statements with other statements} We emphasize that this evaluation is not the same as the usual left-to-right lazy evaluation of logical expressions, because expressions involving $\Sigma$-statements are not treated the same way as usual boolean -expressions: they are evaluated last and in zero-knowledge. +expressions: they are evaluated last and in zero-knowledge. In fact, $\texttt{pkA}$ is not of type \texttt{Boolean}. It is a constant of type \texttt{SigmaProp} with the concrete value @@ -232,10 +232,10 @@ \subsection{Accessing the Context and Box Contents} Note that the script does not prevent the \texttt{friend} box from being spent on its own, in which case the output box protected by the script above will become not spendable. -We can be more permissive and allow for other inputs in addition to the friend box. To do so, we will examine the input collection using the \texttt{exists} operator, which applies a boolean function to each collection element until it finds one that satisfies the function or finds that none exists. To define a function, we use lambda syntax; the argument type (in this case \texttt{Box}) is specified with a colon after the argument name \texttt{inputBox}. We name the function using the \texttt{val} keyword. +We can be more permissive and allow for other inputs in addition to the friend box. To do so, we will examine the input collection using the \texttt{exists} operator, which applies a boolean function to each collection element until it finds one that satisfies the function or finds that none exists. To define a function, we use lambda syntax; the argument type (in this case \texttt{Box}) is specified with a colon after the argument name \texttt{inputBox}. We name the function using the \texttt{def} keyword. \begin{verbatim} { - def isFriend(inputBox: Box) = inputBox.id == friend.id + def isFriend(inputBox: Box) = inputBox.id == friend.id INPUTS.exists (isFriend) } \end{verbatim} @@ -248,7 +248,7 @@ \subsection{Accessing the Context and Box Contents} \paragraph{Example: crowdfunding} -Access to the context allows us to create a script for the following crowdfunding situation: a project backer (with key \texttt{backerPubKey}) wishes to give money to a project (with key \texttt{projectPubKey}), but only if the project raises enough money (at least \texttt{minToRaise}) from other sources by a deadline (expressed in terms of \texttt{HEIGHT}). +Access to the context allows us to create a script for the following crowdfunding situation: a project backer (with key \texttt{backerPubKey}) wishes to give money to a project (with key \texttt{projectPubKey}), but only if the project raises enough money (at least \texttt{minToRaise}) from other sources by a deadline (expressed in terms of \texttt{HEIGHT}). To give money to the project, the backer will create an output box protected by the following script. The script contains two conditions: one for the case the deadline has passed (enabling the backer to get the money back) and one for the case it succeeded (enabling the project to spend the money if the amount is at least \texttt{minToRaise} before the deadline). In order to ensure enough money has been raised, the script will search the output collection for a box with a sufficient value going to the \texttt{projectPubKey}. To check where the value of the output box is going, the script will read the script protecting the output box and compare it to the script \texttt{"projectPubKey"} (that is the simple script described in Section~\ref{sec:sigma-statements}); bytes of this script can be obtained by \texttt{projectPubKey.propBytes}. @@ -256,25 +256,25 @@ \subsection{Accessing the Context and Box Contents} { val fundraisingFailure = HEIGHT >= deadline && backerPubKey val enoughRaised = {(outBox: Box) => - outBox.value >= minToRaise && + outBox.value >= minToRaise && outBox.propositionBytes == projectPubKey.propBytes } val fundraisingSuccess = HEIGHT < deadline && - projectPubKey && + projectPubKey && OUTPUTS.exists(enoughRaised) - + fundraisingFailure || fundraisingSuccess } \end{verbatim} -As before, the values of \texttt{deadline}, \texttt{minToRaise}, and the two public keys are defined by the environment map and hardwired into the script at compile time. +As before, the values of \texttt{deadline}, \texttt{minToRaise}, and the two public keys are defined by the environment map and hardwired into the script at compile time. \subsection{Context Extension and Hashing} \label{sec:extension} A context can also contain typed variables that can be retrieved by numerical id using the operator \texttt{getVar}. These variables are supplied by the prover specifically for a given input box (via a \texttt{ContextExtension}) together with the proof for that box. The id can be any one-byte value (from -128 to 127) and is scoped for each box separately (so variable with id 17 for one input box in a transaction is not the same as variable with id 17 for another input box in the same transaction). -Such context enxtensions can be useful, for example, for requiring a spending transaction to produce hash preimages (the BLAKE2b-256 and SHA-256 hash functions can be invoked in \langname, using keywords \texttt{blake2b256} and \texttt{sha256}). For example, +Such context extensions can be useful, for example, for requiring a spending transaction to produce hash preimages (the BLAKE2b-256 and SHA-256 hash functions can be invoked in \langname, using keywords \texttt{blake2b256} and \texttt{sha256}). For example, \begin{verbatim} pkA && blake2b256(getVar[Coll[Byte]](1).get) == hashOutput \end{verbatim} @@ -294,7 +294,7 @@ \subsection{Context Extension and Hashing} From this output, Bob learns \texttt{hx}. He creates a transaction in Alice's blockchain with an output box protected by the following script: \begin{verbatim} - val x = getVar[Coll[Byte]](1).get + val x = getVar[Coll[Byte]](1).get anyOf( Coll( HEIGHT > deadlineAlice && pkB, allOf( Coll( @@ -309,27 +309,27 @@ \subsection{Context Extension and Hashing} Bob is protected, because in order for Alice to claim the value of this box, she must present a hash preimage of \texttt{hx} as a context extension in the transaction that uses this box as input. But once she does, Bob also learns this hash preimage, and is able to claim the value of the box that Alice placed into his blockchain. Note that Bob needs to choose \texttt{deadlineAlice} early enough to make sure that he is able to learn the preimage of \texttt{hx} from the transaction in Alice's block chain, and create a transaction in his blockchain, all before \texttt{deadlineBob} that Alice chose. Note also that \texttt{HEIGHT} in the two scripts is with respect to two different blockchains, which may be growing at a different rate. Bob also needs to make sure that he can use Alice's \texttt{x} as a context extension; to make sure Alice cannot cheat by making this \texttt{x} so long that it will not be allowed as a context extension in his blockchain, he uses the constraint \texttt{x.size < 33}. -The same approach can be used to trade different assets on the same blockchain, in case of multiasset blockchains. However, for transactions on a single blockchain, an alternative approach is also possible. We describe it below. +The same approach can be used to trade different assets on the same blockchain, in case of multi-assets blockchains. However, for transactions on a single blockchain, an alternative approach is also possible. We describe it below. \subsection{Box Registers and Additional Tokens} \label{sec:box-registers} -Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. +Together with its value and protecting script, a box can contain up to 10 numbered registers, \texttt{R0} through \texttt{R9}. The first four of these have fixed meaning, as follows. For a box \texttt{b}, \texttt{b.R0} is the same as \texttt{b.value} and \texttt{b.R1} is the same as \texttt{b.propositionBytes}. -The third register, \texttt{b.R2}, is for specifying additional, secondary tokens contained in the box (the primary token amount is specified in \texttt{b.value}). \texttt{b.R2} contains a collection of pairs, the first element of each pair specifying the token id (as a collection of 32 bytes) and the second element specifying the amount (as a long value). The maximum number of tokens in a box is set to 4. For every token id, the sum of amounts in inputs boxes be no less than the sum of amounts in output boxes. There is one exception to this rule for the creation of new tokens. When a new token type gets created in a transaction, its id is equal to the id of the input box 0. Thus, the exception for the creation of new tokens is that if the token id in some output box is equal to the id of input box 0, then an arbitrary amount of this token can be output. Because each box has a unique id (see Section~\ref{sec:context}, this exception can be applied exactly once per token type. A newly created token can be emitted in a time-controlled fashion---see Section~\ref{sec:self-replicating}. +The third register, \texttt{b.R2}, is for specifying additional, secondary tokens contained in the box (the primary token amount is specified in \texttt{b.value}). \texttt{b.R2} contains a collection of pairs, the first element of each pair specifying the token id (as a collection of 32 bytes) and the second element specifying the amount (as a long value). The maximum number of tokens in a box is set to 4. For every token id, the sum of amounts in inputs boxes must be no less than the sum of amounts in output boxes. There is one exception to this rule for the creation of new tokens. When a new token type gets created in a transaction, its id is equal to the id of the input box 0. Thus, the exception for the creation of new tokens is that if the token id in some output box is equal to the id of input box 0, then an arbitrary amount of this token can be output. Because each box has a unique id (see Section~\ref{sec:context}, this exception can be applied exactly once per token type. A newly created token can be emitted in a time-controlled fashion---see Section~\ref{sec:self-replicating}. The fourth register, \texttt{b.R3}, contains a pair of integer and 34-byte collection~(its type then is $(Int, Coll[Byte])$). The collection specifies the 32-byte unique transaction id where this box appears as an output followed by a 2-byte sequence number of this box in the \texttt{OUTPUTS} collection of that transaction. This ensures that each box has unique \texttt{R3} and therefore a unique \texttt{id} as long as there are no hash collisions (because the \texttt{id} of the box is computed by hashing its content, including \texttt{R3}). The first element of the pair contains creation height provided by user created the box. This height could only be less or equal than real inclusion height~ -(a transaction could not be included into the blockchain if it contains an output with creation height being no more than current blockchain height). +(a transaction could not be included into the blockchain if it contains an output with creation height being no greater than current blockchain height). -The remaining six registers can be used arbitrarily. +The remaining six registers can be used arbitrarily. To access a register, the type of the register needs to be specified in brackets following the register number (for example, \texttt{b.R4[Int]}). Note that \texttt{b.R4[T]} is of type \texttt{Option[T]}; \texttt{b.R4[T].isDefined} indicates whether it actually contains a value of type \texttt{T}, and \texttt{b.R4[T].get} obtains this value. -In addition to registers, scripts can access two serialized versions of the box: \texttt{b.bytes} is a serialization of the entire box including all its registers, and \texttt{b.byteWithNoRef}, which the same but without the transation identifier and the output index~(so that a box can be viewed independently of where it appeared. +In addition to registers, scripts can access two serialized versions of the box: \texttt{b.bytes} is a serialization of the entire box including all its registers, and \texttt{b.bytesWithNoRef}, which the same but without the transaction identifier and the output index~(so that a box can be viewed independently of where it appeared. \paragraph{Example: atomic exchange on a single block chain} -These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Alice will give Bob 60 tokens of type \texttt{token1} (this type is mapped an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with following script: +These box registers provide additional capabilities to \langname. Consider, for example, Alice and Bob who want to exchange tokens: they agree that Alice will give Bob 60 tokens of type \texttt{token1} (this type is mapped to an actual token id in the environment map) in exchange for 100 Ergo tokens. Alice could create an output box with value 100 and protect it with the following script: \begin{verbatim} (HEIGHT > deadline && pkA) || { @@ -344,12 +344,12 @@ \subsection{Box Registers and Additional Tokens} \end{verbatim} -This script ensures that the box can be spent only in a transaction that produces an output with 60 tokens of type token1 and gives them to Alice (Alice can reclaim the box after the deadline). -Moreover, the last condition (\texttt{OUTPUTS(0).R4[Col[Byte]].get == SELF.id}) ensures that if Alice has multiple such boxes outstanding at a given time, each will produce a separate output that identifies the corresponding input. This condition prevents the following attack: if Alice has two such boxes outstanding but the last condition is not present, then they can be both used in a single transaction that contains just one output with 60 tokens of type ``token1" --- the script of each input box will be individually satisfied, but Alice will get less only half of what owed to her. +This script ensures that the box can be spent only in a transaction that produces an output with 60 tokens of type \texttt{token1} and gives them to Alice (Alice can reclaim the box after the deadline). +Moreover, the last condition (\texttt{OUTPUTS(0).R4[Col[Byte]].get == SELF.id}) ensures that if Alice has multiple such boxes outstanding at a given time, each will produce a separate output that identifies the corresponding input. This condition prevents the following attack: if Alice has two such boxes outstanding but the last condition is not present, then they can be both used in a single transaction that contains just one output with 60 tokens of type \texttt{token1} --- the script of each input box will be individually satisfied, but Alice will get less only half of what owed to her. Bob, similarly, could create an output box with value about 0 and 60 tokens of type \texttt{token1} and protect it by the following script: \begin{verbatim} - (HEIGHT > deadline && pkB) || + (HEIGHT > deadline && pkB) || allOf( Coll( OUTPUTS(1).value >= 100L, OUTPUTS(1).propositionBytes == pkB.propBytes, @@ -365,13 +365,13 @@ \subsection{Self-Replicating Code} \paragraph{Example: time-controlled coin emission} In this example, we will create a self-replicating box that emits new coins at each time step in order to add to the total amount of currency available. This box appears as an output in the genesis block (at height 0) of the blockchain; all "new" coins come from this box or its descendants, thus maintaining the invariant that for every transaction after the genesis block, the combined value of all inputs is equal to the combined value of all outputs. -The value of this box initially is equal to the total amount of currency that will eventually be available. This value will go down by a prespecified amount each time this box is transacted. Because in each transaction, the sum of input values must equal the sum of output values, when the value of this box goes down, the difference must be claimed by someone. The box is set up to allow to the difference to go anyone---presumably, it will be claimed by the miner who created the block that contains the transaction. This box will store, in \texttt{R4}, the height at which it was created. Using this information, it will be able to determine how much value to emit. The box will be set to emit +The value of this box initially is equal to the total amount of currency that will eventually be available. This value will go down by a prespecified amount each time this box is transacted. Because in each transaction, the sum of input values must equal the sum of output values, when the value of this box goes down, the difference must be claimed by someone. The box is set up to allow the difference to go to anyone---presumably, it will be claimed by the miner who created the block that contains the transaction. This box will store, in \texttt{R4}, the height at which it was created. Using this information, it will be able to determine how much value to emit. The box will be set to emit a fixed amount specified by \texttt{fixedRate} per block until \texttt{HEIGHT} reaches \texttt{fixedRatePeriod}, and a linearly decreasing amount thereafter. The script will verify that the output box has the same script as itself, and that the new height stored in \texttt{R4} and the new value are correctly computed (and that the height has increased, so that a miner cannot emit more than once per block). The following script accomplishes this goal: \begin{verbatim} { val epoch = 1 + ((HEIGHT - fixedRatePeriod) / epochLength) val out = OUTPUTS(0) - val coinsToIssue = if(HEIGHT < fixedRatePeriod) fixedRate + val coinsToIssue = if(HEIGHT < fixedRatePeriod) fixedRate else fixedRate - (oneEpochReduction * epoch) val correctCoinsConsumed = coinsToIssue == (SELF.value - out.value) val sameScriptRule = SELF.propositionBytes == out.propositionBytes @@ -379,8 +379,8 @@ \subsection{Self-Replicating Code} val heightCorrect = out.R4[Int].get == HEIGHT val lastCoins = SELF.value <= oneEpochReduction allOf(Coll( - heightCorrect, - heightIncreased, + heightCorrect, + heightIncreased, sameScriptRule, correctCoinsConsumed)) || (heightIncreased && lastCoins) @@ -544,18 +544,18 @@ \section{Implementation of Noninteractive $\Sigma$-protocols for an arbitrary An \subsection{Background} -In this section, we explain in detail how the $\Sigma$-protocol proving and verifying is implemented. Consider the tree after the evaluation process, as described in Section~\ref{sec:impl}, reduces it to only $\Sigma$-protocol nodes. Then the leaves of the tree are atomic $\Sigma$-protocols, and non-leaves are of one of three types: $\andnode$ (corresponding to \texttt{\&\&} or \texttt{allOf} in \langname), $\ornode$ (corresponding to \texttt{||} or \texttt{anyOf} in \langname), or $\tnode(k)$ (corresponding to \texttt{atLeast} in \langname). +In this section, we explain in detail how the $\Sigma$-protocol proving and verifying is implemented. Consider the tree after the evaluation process, as described in Section~\ref{sec:impl}, reduces it to only $\Sigma$-protocol nodes. Then the leaves of the tree are atomic $\Sigma$-protocols, and non-leaves are of one of three types: $\andnode$ (corresponding to \texttt{\&\&} or \texttt{allOf} in \langname), $\ornode$ (corresponding to \texttt{||} or \texttt{anyOf} in \langname), or $\tnode(k)$ (corresponding to \texttt{atLeast} in \langname). The meaning of the proof corresponding to $\tnode(k)$ is ``the prover knows witnesses for at least $k$ children of this node''. Semantically, $\andnode$ and $\ornode$ are simply special cases of $\tnode$: the meaning of the proof corresponding $\andnode$ (respectively, $\ornode)$ is ``the prover knows witnesses for all children (respectively, at least one child) of this node''. However, $\andnode$ and $\ornode$ are implemented differently from $\tnode$ for efficiency. -For the purposes of this description, it does not matter what specific atomic $\Sigma$-protocols are used at the leaves. They can be, for example, \texttt{proveDlog(x)} for proving knowledge of the discrete logarithm $w$ of $x=g^w$, or \texttt{proveDHtuple(g1, g2, u1, u2)} for proving that $(g_1, g_2, u_1, u_2)$ form a Diffie-Hellman tuple, i.e., $\exists w \mbox{\ such\ that\ } u_1 = g_1^w \, \wedge \, u_2 = h_2^w$. In general, we will assume the prover has some secret $w$ and wants to prove some property of it. +For the purposes of this description, it does not matter what specific atomic $\Sigma$-protocols are used at the leaves. They can be, for example, \texttt{proveDlog(x)} for proving knowledge of the discrete logarithm $w$ of $x=g^w$, or \texttt{proveDHtuple(g1, g2, u1, u2)} for proving that $(g_1, g_2, u_1, u_2)$ form a Diffie-Hellman tuple, i.e., $\exists w \mbox{\ such\ that\ } u_1 = g_1^w \, \wedge \, u_2 = h_2^w$. In general, we will assume the prover has some secret $w$ and wants to prove some property of it. -A $\Sigma$-protocol consist of three messages: +A $\Sigma$-protocol consist of three messages: \begin{itemize} \item a \emph{commitment} $a$ sent from the Prover to the Verifier (computed using the witness $w$ and some freshly generated secret randomness $r$ by the Prover's first step algorithm); \item a uniformly random \emph{challenge} $e$ sent from the Verifier to the Prover; -\item and a \emph{response} $z$ sent from the Prover to the Verifier (computed using the randomness $r$, the challenge $e$, and the witness $w$ by the Prover's second step algorithm). +\item and a \emph{response} $z$ sent from the Prover to the Verifier (computed using the randomness $r$, the challenge $e$, and the witness $w$ by the Prover's second step algorithm). \end{itemize} The verifier then checks that the triple $(a, e, z)$ satisfies some formula and, if so, accepts the proof. @@ -563,9 +563,9 @@ \subsection{Background} $\Sigma$-protocols have the property of \emph{special honest-verifier zero-knowledge}. For the purposes of this description, it means that given a random $e$, it is possible to compute $a$ and $z$ without knowing the secret witness $w$ that is normally needed by the prover. This computation is called ``simulation''. Moreover, the triple $(a, e, z)$ computed via simulation is distributed identically to the triple $(a, e, z)$ that results form a $\Sigma$ protocol that is run by the honest prover (who knows the secret witness $w$) and verifier (who generates a uniform $e$). The trick that makes simulation possible is that the response $z$ is chosen by the simulator before commitment $a$, in contrast to the prover, who is forced to choose $a$ before $z$. -$\Sigma$-protocols used in this work must also satisfy a property of \emph{special soundness}, which means that given two triples $(a_1, e_1, z_1)$ and $(a_2, e_2, z_2)$ that are both accepted by the verifier, and $a_1=a_2$ while $e_1\neq e_2$, it is possible to compute the Prover's secret witness $w$ in polynomial time and thus directly verify that the statement claimed by the Prover is true. Note that this computation is never performed, because the Prover will never actually answer two different challenges $e_1\neq e_2$ for the same commitment $a_1=a_2$. +$\Sigma$-protocols used in this work must also satisfy a property of \emph{special soundness}, which means that given two triples $(a_1, e_1, z_1)$ and $(a_2, e_2, z_2)$ that are both accepted by the verifier, and $a_1=a_2$ while $e_1\neq e_2$, it is possible to compute the Prover's secret witness $w$ in polynomial time and thus directly verify that the statement claimed by the Prover is true. Note that this computation is never performed, because the Prover will never actually answer two different challenges $e_1\neq e_2$ for the same commitment $a_1=a_2$. -In order for composition of $\Sigma$-protocols using $\andnode$, $\ornode$, and $\tnode$ to work, the challenge $e$ in all protocols must be a binary string of the same length, which we will call $t$. (This implies that if the Verifier performs arithmetic modulo $q$ on $e$, then $2^t
15 degrees, money are going to Alice, otherwise to Bob. - + We consider that for validating transaction only limited number of last headers and the spending transaction should be enough, in addition to outputs being spent by the transaction. Thus there is no need for knowledge of an arbitrary output. To show the coin of the weather service in the spending transaction, outputs from Alice and Bob are referencing to the coin by using Merkle proofs against UTXO set root hash of the latest known block. - + A tricky moment is how Alice and Bob can be sure that a coin is indeed created by the service, having just the coin (and also service's public key x = $g^w$, where service's secret w is not known. - + For that, we consider that the service creates a coin with registers of following semantics (R0 and R1 are standard): - + R1 - coin amount R2 - protecting script, which is the pubkey of the service, $x = g^w$ R3 - temperature data, number R4 - $a = g^r$, where r is secret random nonce R5 - $z = r + ew mod q$ R6 - timestamp - + Then Alice and Bob are requiring from the coin that the following equation holds: $g^z = a * x^e$, where $e = hash(R3 ++ R6)$ - + Thus Alice, for example, is created a coin with the following statement (we skip timeouts for simplicity): "the coin is spendable by presenting a proof of Alice's private key knowledge if against UTXO set root hash for the last known block there is a coin along with a Merkle proof, for which following requirements hold: $R2 = dlog(x) /\ g^(R5) = R4 * x^(hash(R3 ++ R6)) /\ (R3) > 15$. Similarly, the coin is spendable by a proof of knowledge of the Bob's private key if all the same conditions are met but $(R3) <= 15$.". - + The Bob can create a box with the same guarding conditions. However, if Alice's box is already in the state, then Bob can stick to it by using the trick from the "along with a brother" test. \section{Open Questions} diff --git a/docs/zerojoin/CoinJoin.jpg b/docs/zerojoin/CoinJoin.jpg new file mode 100644 index 0000000000..bf9ecbd7c2 Binary files /dev/null and b/docs/zerojoin/CoinJoin.jpg differ diff --git a/docs/zerojoin/ErgoMix.jpg b/docs/zerojoin/ErgoMix.jpg new file mode 100644 index 0000000000..e8e34e0e0a Binary files /dev/null and b/docs/zerojoin/ErgoMix.jpg differ diff --git a/docs/zerojoin/ErgoMixFlow.jpg b/docs/zerojoin/ErgoMixFlow.jpg new file mode 100644 index 0000000000..b27f26ebb3 Binary files /dev/null and b/docs/zerojoin/ErgoMixFlow.jpg differ diff --git a/docs/zerojoin/MixingTokens.jpg b/docs/zerojoin/MixingTokens.jpg new file mode 100644 index 0000000000..5eb9510315 Binary files /dev/null and b/docs/zerojoin/MixingTokens.jpg differ diff --git a/docs/zerojoin/ZeroCoin.jpg b/docs/zerojoin/ZeroCoin.jpg new file mode 100644 index 0000000000..ff84541872 Binary files /dev/null and b/docs/zerojoin/ZeroCoin.jpg differ diff --git a/docs/zerojoin/compile.sh b/docs/zerojoin/compile.sh new file mode 100755 index 0000000000..fa58705e79 --- /dev/null +++ b/docs/zerojoin/compile.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env sh + +command -v pdflatex && command -v bibtex +if [[ "$?" != 0 ]]; then + echo "Command 'pdflatex' or 'bibtex' not exist, both must be installed. For Ubuntu, try:" + echo "sudo apt install texlive-latex-base texlive-binaries" + echo + echo "You may also need to install additional packages like fonts, etc. For Ubuntu, try:" + echo "sudo apt-get install texlive-fonts-recommended latex-xcolor texlive-latex-extra cm-super" + exit 1; +fi + +pdflatex main +bibtex main +pdflatex main +pdflatex main +rm main.aux +rm main.out +rm main.toc +rm main.log diff --git a/docs/zerojoin/llncs.cls b/docs/zerojoin/llncs.cls new file mode 100644 index 0000000000..2d29b24e3a --- /dev/null +++ b/docs/zerojoin/llncs.cls @@ -0,0 +1,1207 @@ +% LLNCS DOCUMENT CLASS -- version 2.17 (12-Jul-2010) +% Springer Verlag LaTeX2e support for Lecture Notes in Computer Science +% +%% +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\NeedsTeXFormat{LaTeX2e}[1995/12/01] +\ProvidesClass{llncs}[2010/07/12 v2.17 +^^J LaTeX document class for Lecture Notes in Computer Science] +% Options +\let\if@envcntreset\iffalse +\DeclareOption{envcountreset}{\let\if@envcntreset\iftrue} +\DeclareOption{citeauthoryear}{\let\citeauthoryear=Y} +\DeclareOption{oribibl}{\let\oribibl=Y} +\let\if@custvec\iftrue +\DeclareOption{orivec}{\let\if@custvec\iffalse} +\let\if@envcntsame\iffalse +\DeclareOption{envcountsame}{\let\if@envcntsame\iftrue} +\let\if@envcntsect\iffalse +\DeclareOption{envcountsect}{\let\if@envcntsect\iftrue} +\let\if@runhead\iffalse +\DeclareOption{runningheads}{\let\if@runhead\iftrue} + +\let\if@openright\iftrue +\let\if@openbib\iffalse +\DeclareOption{openbib}{\let\if@openbib\iftrue} + +% languages +\let\switcht@@therlang\relax +\def\ds@deutsch{\def\switcht@@therlang{\switcht@deutsch}} +\def\ds@francais{\def\switcht@@therlang{\switcht@francais}} + +\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} + +\ProcessOptions + +\LoadClass[twoside]{article} +\RequirePackage{multicol} % needed for the list of participants, index +\RequirePackage{aliascnt} + +\setlength{\textwidth}{12.2cm} +\setlength{\textheight}{19.3cm} +\renewcommand\@pnumwidth{2em} +\renewcommand\@tocrmarg{3.5em} +% +\def\@dottedtocline#1#2#3#4#5{% + \ifnum #1>\c@tocdepth \else + \vskip \z@ \@plus.2\p@ + {\leftskip #2\relax \rightskip \@tocrmarg \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \parindent #2\relax\@afterindenttrue + \interlinepenalty\@M + \leavevmode + \@tempdima #3\relax + \advance\leftskip \@tempdima \null\nobreak\hskip -\leftskip + {#4}\nobreak + \leaders\hbox{$\m@th + \mkern \@dotsep mu\hbox{.}\mkern \@dotsep + mu$}\hfill + \nobreak + \hb@xt@\@pnumwidth{\hfil\normalfont \normalcolor #5}% + \par}% + \fi} +% +\def\switcht@albion{% +\def\abstractname{Abstract.} +\def\ackname{Acknowledgement.} +\def\andname{and} +\def\lastandname{\unskip, and} +\def\appendixname{Appendix} +\def\chaptername{Chapter} +\def\claimname{Claim} +\def\conjecturename{Conjecture} +\def\contentsname{Table of Contents} +\def\corollaryname{Corollary} +\def\definitionname{Definition} +\def\examplename{Example} +\def\exercisename{Exercise} +\def\figurename{Fig.} +\def\keywordname{{\bf Keywords:}} +\def\indexname{Index} +\def\lemmaname{Lemma} +\def\contriblistname{List of Contributors} +\def\listfigurename{List of Figures} +\def\listtablename{List of Tables} +\def\mailname{{\it Correspondence to\/}:} +\def\noteaddname{Note added in proof} +\def\notename{Note} +\def\partname{Part} +\def\problemname{Problem} +\def\proofname{Proof} +\def\propertyname{Property} +\def\propositionname{Proposition} +\def\questionname{Question} +\def\remarkname{Remark} +\def\seename{see} +\def\solutionname{Solution} +\def\subclassname{{\it Subject Classifications\/}:} +\def\tablename{Table} +\def\theoremname{Theorem}} +\switcht@albion +% Names of theorem like environments are already defined +% but must be translated if another language is chosen +% +% French section +\def\switcht@francais{%\typeout{On parle francais.}% + \def\abstractname{R\'esum\'e.}% + \def\ackname{Remerciements.}% + \def\andname{et}% + \def\lastandname{ et}% + \def\appendixname{Appendice} + \def\chaptername{Chapitre}% + \def\claimname{Pr\'etention}% + \def\conjecturename{Hypoth\`ese}% + \def\contentsname{Table des mati\`eres}% + \def\corollaryname{Corollaire}% + \def\definitionname{D\'efinition}% + \def\examplename{Exemple}% + \def\exercisename{Exercice}% + \def\figurename{Fig.}% + \def\keywordname{{\bf Mots-cl\'e:}} + \def\indexname{Index} + \def\lemmaname{Lemme}% + \def\contriblistname{Liste des contributeurs} + \def\listfigurename{Liste des figures}% + \def\listtablename{Liste des tables}% + \def\mailname{{\it Correspondence to\/}:} + \def\noteaddname{Note ajout\'ee \`a l'\'epreuve}% + \def\notename{Remarque}% + \def\partname{Partie}% + \def\problemname{Probl\`eme}% + \def\proofname{Preuve}% + \def\propertyname{Caract\'eristique}% +%\def\propositionname{Proposition}% + \def\questionname{Question}% + \def\remarkname{Remarque}% + \def\seename{voir} + \def\solutionname{Solution}% + \def\subclassname{{\it Subject Classifications\/}:} + \def\tablename{Tableau}% + \def\theoremname{Th\'eor\`eme}% +} +% +% German section +\def\switcht@deutsch{%\typeout{Man spricht deutsch.}% + \def\abstractname{Zusammenfassung.}% + \def\ackname{Danksagung.}% + \def\andname{und}% + \def\lastandname{ und}% + \def\appendixname{Anhang}% + \def\chaptername{Kapitel}% + \def\claimname{Behauptung}% + \def\conjecturename{Hypothese}% + \def\contentsname{Inhaltsverzeichnis}% + \def\corollaryname{Korollar}% +%\def\definitionname{Definition}% + \def\examplename{Beispiel}% + \def\exercisename{\"Ubung}% + \def\figurename{Abb.}% + \def\keywordname{{\bf Schl\"usselw\"orter:}} + \def\indexname{Index} +%\def\lemmaname{Lemma}% + \def\contriblistname{Mitarbeiter} + \def\listfigurename{Abbildungsverzeichnis}% + \def\listtablename{Tabellenverzeichnis}% + \def\mailname{{\it Correspondence to\/}:} + \def\noteaddname{Nachtrag}% + \def\notename{Anmerkung}% + \def\partname{Teil}% +%\def\problemname{Problem}% + \def\proofname{Beweis}% + \def\propertyname{Eigenschaft}% +%\def\propositionname{Proposition}% + \def\questionname{Frage}% + \def\remarkname{Anmerkung}% + \def\seename{siehe} + \def\solutionname{L\"osung}% + \def\subclassname{{\it Subject Classifications\/}:} + \def\tablename{Tabelle}% +%\def\theoremname{Theorem}% +} + +% Ragged bottom for the actual page +\def\thisbottomragged{\def\@textbottom{\vskip\z@ plus.0001fil +\global\let\@textbottom\relax}} + +\renewcommand\small{% + \@setfontsize\small\@ixpt{11}% + \abovedisplayskip 8.5\p@ \@plus3\p@ \@minus4\p@ + \abovedisplayshortskip \z@ \@plus2\p@ + \belowdisplayshortskip 4\p@ \@plus2\p@ \@minus2\p@ + \def\@listi{\leftmargin\leftmargini + \parsep 0\p@ \@plus1\p@ \@minus\p@ + \topsep 8\p@ \@plus2\p@ \@minus4\p@ + \itemsep0\p@}% + \belowdisplayskip \abovedisplayskip +} + +\frenchspacing +\widowpenalty=10000 +\clubpenalty=10000 + +\setlength\oddsidemargin {63\p@} +\setlength\evensidemargin {63\p@} +\setlength\marginparwidth {90\p@} + +\setlength\headsep {16\p@} + +\setlength\footnotesep{7.7\p@} +\setlength\textfloatsep{8mm\@plus 2\p@ \@minus 4\p@} +\setlength\intextsep {8mm\@plus 2\p@ \@minus 2\p@} + +\setcounter{secnumdepth}{2} + +\newcounter {chapter} +\renewcommand\thechapter {\@arabic\c@chapter} + +\newif\if@mainmatter \@mainmattertrue +\newcommand\frontmatter{\cleardoublepage + \@mainmatterfalse\pagenumbering{Roman}} +\newcommand\mainmatter{\cleardoublepage + \@mainmattertrue\pagenumbering{arabic}} +\newcommand\backmatter{\if@openright\cleardoublepage\else\clearpage\fi + \@mainmatterfalse} + +\renewcommand\part{\cleardoublepage + \thispagestyle{empty}% + \if@twocolumn + \onecolumn + \@tempswatrue + \else + \@tempswafalse + \fi + \null\vfil + \secdef\@part\@spart} + +\def\@part[#1]#2{% + \ifnum \c@secnumdepth >-2\relax + \refstepcounter{part}% + \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% + \else + \addcontentsline{toc}{part}{#1}% + \fi + \markboth{}{}% + {\centering + \interlinepenalty \@M + \normalfont + \ifnum \c@secnumdepth >-2\relax + \huge\bfseries \partname~\thepart + \par + \vskip 20\p@ + \fi + \Huge \bfseries #2\par}% + \@endpart} +\def\@spart#1{% + {\centering + \interlinepenalty \@M + \normalfont + \Huge \bfseries #1\par}% + \@endpart} +\def\@endpart{\vfil\newpage + \if@twoside + \null + \thispagestyle{empty}% + \newpage + \fi + \if@tempswa + \twocolumn + \fi} + +\newcommand\chapter{\clearpage + \thispagestyle{empty}% + \global\@topnum\z@ + \@afterindentfalse + \secdef\@chapter\@schapter} +\def\@chapter[#1]#2{\ifnum \c@secnumdepth >\m@ne + \if@mainmatter + \refstepcounter{chapter}% + \typeout{\@chapapp\space\thechapter.}% + \addcontentsline{toc}{chapter}% + {\protect\numberline{\thechapter}#1}% + \else + \addcontentsline{toc}{chapter}{#1}% + \fi + \else + \addcontentsline{toc}{chapter}{#1}% + \fi + \chaptermark{#1}% + \addtocontents{lof}{\protect\addvspace{10\p@}}% + \addtocontents{lot}{\protect\addvspace{10\p@}}% + \if@twocolumn + \@topnewpage[\@makechapterhead{#2}]% + \else + \@makechapterhead{#2}% + \@afterheading + \fi} +\def\@makechapterhead#1{% +% \vspace*{50\p@}% + {\centering + \ifnum \c@secnumdepth >\m@ne + \if@mainmatter + \large\bfseries \@chapapp{} \thechapter + \par\nobreak + \vskip 20\p@ + \fi + \fi + \interlinepenalty\@M + \Large \bfseries #1\par\nobreak + \vskip 40\p@ + }} +\def\@schapter#1{\if@twocolumn + \@topnewpage[\@makeschapterhead{#1}]% + \else + \@makeschapterhead{#1}% + \@afterheading + \fi} +\def\@makeschapterhead#1{% +% \vspace*{50\p@}% + {\centering + \normalfont + \interlinepenalty\@M + \Large \bfseries #1\par\nobreak + \vskip 40\p@ + }} + +\renewcommand\section{\@startsection{section}{1}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {12\p@ \@plus 4\p@ \@minus 4\p@}% + {\normalfont\large\bfseries\boldmath + \rightskip=\z@ \@plus 8em\pretolerance=10000 }} +\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {8\p@ \@plus 4\p@ \@minus 4\p@}% + {\normalfont\normalsize\bfseries\boldmath + \rightskip=\z@ \@plus 8em\pretolerance=10000 }} +\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% + {-18\p@ \@plus -4\p@ \@minus -4\p@}% + {-0.5em \@plus -0.22em \@minus -0.1em}% + {\normalfont\normalsize\bfseries\boldmath}} +\renewcommand\paragraph{\@startsection{paragraph}{4}{\z@}% + {-12\p@ \@plus -4\p@ \@minus -4\p@}% + {-0.5em \@plus -0.22em \@minus -0.1em}% + {\normalfont\normalsize\itshape}} +\renewcommand\subparagraph[1]{\typeout{LLNCS warning: You should not use + \string\subparagraph\space with this class}\vskip0.5cm +You should not use \verb|\subparagraph| with this class.\vskip0.5cm} + +\DeclareMathSymbol{\Gamma}{\mathalpha}{letters}{"00} +\DeclareMathSymbol{\Delta}{\mathalpha}{letters}{"01} +\DeclareMathSymbol{\Theta}{\mathalpha}{letters}{"02} +\DeclareMathSymbol{\Lambda}{\mathalpha}{letters}{"03} +\DeclareMathSymbol{\Xi}{\mathalpha}{letters}{"04} +\DeclareMathSymbol{\Pi}{\mathalpha}{letters}{"05} +\DeclareMathSymbol{\Sigma}{\mathalpha}{letters}{"06} +\DeclareMathSymbol{\Upsilon}{\mathalpha}{letters}{"07} +\DeclareMathSymbol{\Phi}{\mathalpha}{letters}{"08} +\DeclareMathSymbol{\Psi}{\mathalpha}{letters}{"09} +\DeclareMathSymbol{\Omega}{\mathalpha}{letters}{"0A} + +\let\footnotesize\small + +\if@custvec +\def\vec#1{\mathchoice{\mbox{\boldmath$\displaystyle#1$}} +{\mbox{\boldmath$\textstyle#1$}} +{\mbox{\boldmath$\scriptstyle#1$}} +{\mbox{\boldmath$\scriptscriptstyle#1$}}} +\fi + +\def\squareforqed{\hbox{\rlap{$\sqcap$}$\sqcup$}} +\def\qed{\ifmmode\squareforqed\else{\unskip\nobreak\hfil +\penalty50\hskip1em\null\nobreak\hfil\squareforqed +\parfillskip=0pt\finalhyphendemerits=0\endgraf}\fi} + +\def\getsto{\mathrel{\mathchoice {\vcenter{\offinterlineskip +\halign{\hfil +$\displaystyle##$\hfil\cr\gets\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr\gets +\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr\gets +\cr\to\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +\gets\cr\to\cr}}}}} +\def\lid{\mathrel{\mathchoice {\vcenter{\offinterlineskip\halign{\hfil +$\displaystyle##$\hfil\cr<\cr\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr<\cr +\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr<\cr +\noalign{\vskip1pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +<\cr +\noalign{\vskip0.9pt}=\cr}}}}} +\def\gid{\mathrel{\mathchoice {\vcenter{\offinterlineskip\halign{\hfil +$\displaystyle##$\hfil\cr>\cr\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr>\cr +\noalign{\vskip1.2pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr>\cr +\noalign{\vskip1pt}=\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +>\cr +\noalign{\vskip0.9pt}=\cr}}}}} +\def\grole{\mathrel{\mathchoice {\vcenter{\offinterlineskip +\halign{\hfil +$\displaystyle##$\hfil\cr>\cr\noalign{\vskip-1pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\textstyle##$\hfil\cr +>\cr\noalign{\vskip-1pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptstyle##$\hfil\cr +>\cr\noalign{\vskip-0.8pt}<\cr}}} +{\vcenter{\offinterlineskip\halign{\hfil$\scriptscriptstyle##$\hfil\cr +>\cr\noalign{\vskip-0.3pt}<\cr}}}}} +\def\bbbr{{\rm I\!R}} %reelle Zahlen +\def\bbbm{{\rm I\!M}} +\def\bbbn{{\rm I\!N}} %natuerliche Zahlen +\def\bbbf{{\rm I\!F}} +\def\bbbh{{\rm I\!H}} +\def\bbbk{{\rm I\!K}} +\def\bbbp{{\rm I\!P}} +\def\bbbone{{\mathchoice {\rm 1\mskip-4mu l} {\rm 1\mskip-4mu l} +{\rm 1\mskip-4.5mu l} {\rm 1\mskip-5mu l}}} +\def\bbbc{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm C$}\hbox{\hbox +to0pt{\kern0.4\wd0\vrule height0.9\ht0\hss}\box0}}}} +\def\bbbq{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm +Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.8\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.8\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.7\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm Q$}\hbox{\raise +0.15\ht0\hbox to0pt{\kern0.4\wd0\vrule height0.7\ht0\hss}\box0}}}} +\def\bbbt{{\mathchoice {\setbox0=\hbox{$\displaystyle\rm +T$}\hbox{\hbox to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm T$}\hbox{\hbox +to0pt{\kern0.3\wd0\vrule height0.9\ht0\hss}\box0}}}} +\def\bbbs{{\mathchoice +{\setbox0=\hbox{$\displaystyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\hbox +to0pt{\kern0.55\wd0\vrule height0.5\ht0\hss}\box0}} +{\setbox0=\hbox{$\textstyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\hbox +to0pt{\kern0.55\wd0\vrule height0.5\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptstyle \rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.35\wd0\vrule height0.45\ht0\hss}\raise0.05\ht0\hbox +to0pt{\kern0.5\wd0\vrule height0.45\ht0\hss}\box0}} +{\setbox0=\hbox{$\scriptscriptstyle\rm S$}\hbox{\raise0.5\ht0\hbox +to0pt{\kern0.4\wd0\vrule height0.45\ht0\hss}\raise0.05\ht0\hbox +to0pt{\kern0.55\wd0\vrule height0.45\ht0\hss}\box0}}}} +\def\bbbz{{\mathchoice {\hbox{$\mathsf\textstyle Z\kern-0.4em Z$}} +{\hbox{$\mathsf\textstyle Z\kern-0.4em Z$}} +{\hbox{$\mathsf\scriptstyle Z\kern-0.3em Z$}} +{\hbox{$\mathsf\scriptscriptstyle Z\kern-0.2em Z$}}}} + +\let\ts\, + +\setlength\leftmargini {17\p@} +\setlength\leftmargin {\leftmargini} +\setlength\leftmarginii {\leftmargini} +\setlength\leftmarginiii {\leftmargini} +\setlength\leftmarginiv {\leftmargini} +\setlength \labelsep {.5em} +\setlength \labelwidth{\leftmargini} +\addtolength\labelwidth{-\labelsep} + +\def\@listI{\leftmargin\leftmargini + \parsep 0\p@ \@plus1\p@ \@minus\p@ + \topsep 8\p@ \@plus2\p@ \@minus4\p@ + \itemsep0\p@} +\let\@listi\@listI +\@listi +\def\@listii {\leftmargin\leftmarginii + \labelwidth\leftmarginii + \advance\labelwidth-\labelsep + \topsep 0\p@ \@plus2\p@ \@minus\p@} +\def\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii + \advance\labelwidth-\labelsep + \topsep 0\p@ \@plus\p@\@minus\p@ + \parsep \z@ + \partopsep \p@ \@plus\z@ \@minus\p@} + +\renewcommand\labelitemi{\normalfont\bfseries --} +\renewcommand\labelitemii{$\m@th\bullet$} + +\setlength\arraycolsep{1.4\p@} +\setlength\tabcolsep{1.4\p@} + +\def\tableofcontents{\chapter*{\contentsname\@mkboth{{\contentsname}}% + {{\contentsname}}} + \def\authcount##1{\setcounter{auco}{##1}\setcounter{@auth}{1}} + \def\lastand{\ifnum\value{auco}=2\relax + \unskip{} \andname\ + \else + \unskip \lastandname\ + \fi}% + \def\and{\stepcounter{@auth}\relax + \ifnum\value{@auth}=\value{auco}% + \lastand + \else + \unskip, + \fi}% + \@starttoc{toc}\if@restonecol\twocolumn\fi} + +\def\l@part#1#2{\addpenalty{\@secpenalty}% + \addvspace{2em plus\p@}% % space above part line + \begingroup + \parindent \z@ + \rightskip \z@ plus 5em + \hrule\vskip5pt + \large % same size as for a contribution heading + \bfseries\boldmath % set line in boldface + \leavevmode % TeX command to enter horizontal mode. + #1\par + \vskip5pt + \hrule + \vskip1pt + \nobreak % Never break after part entry + \endgroup} + +\def\@dotsep{2} + +\let\phantomsection=\relax + +\def\hyperhrefextend{\ifx\hyper@anchor\@undefined\else +{}\fi} + +\def\addnumcontentsmark#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{\protect\numberline + {\thechapter}#3}{\thepage}\hyperhrefextend}}% +\def\addcontentsmark#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{#3}{\thepage}\hyperhrefextend}}% +\def\addcontentsmarkwop#1#2#3{% +\addtocontents{#1}{\protect\contentsline{#2}{#3}{0}\hyperhrefextend}}% + +\def\@adcmk[#1]{\ifcase #1 \or +\def\@gtempa{\addnumcontentsmark}% + \or \def\@gtempa{\addcontentsmark}% + \or \def\@gtempa{\addcontentsmarkwop}% + \fi\@gtempa{toc}{chapter}% +} +\def\addtocmark{% +\phantomsection +\@ifnextchar[{\@adcmk}{\@adcmk[3]}% +} + +\def\l@chapter#1#2{\addpenalty{-\@highpenalty} + \vskip 1.0em plus 1pt \@tempdima 1.5em \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima \hskip -\leftskip + {\large\bfseries\boldmath#1}\ifx0#2\hfil\null + \else + \nobreak + \leaders\hbox{$\m@th \mkern \@dotsep mu.\mkern + \@dotsep mu$}\hfill + \nobreak\hbox to\@pnumwidth{\hss #2}% + \fi\par + \penalty\@highpenalty \endgroup} + +\def\l@title#1#2{\addpenalty{-\@highpenalty} + \addvspace{8pt plus 1pt} + \@tempdima \z@ + \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \parfillskip -\rightskip \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima \hskip -\leftskip + #1\nobreak + \leaders\hbox{$\m@th \mkern \@dotsep mu.\mkern + \@dotsep mu$}\hfill + \nobreak\hbox to\@pnumwidth{\hss #2}\par + \penalty\@highpenalty \endgroup} + +\def\l@author#1#2{\addpenalty{\@highpenalty} + \@tempdima=15\p@ %\z@ + \begingroup + \parindent \z@ \rightskip \@tocrmarg + \advance\rightskip by 0pt plus 2cm + \pretolerance=10000 + \leavevmode \advance\leftskip\@tempdima %\hskip -\leftskip + \textit{#1}\par + \penalty\@highpenalty \endgroup} + +\setcounter{tocdepth}{0} +\newdimen\tocchpnum +\newdimen\tocsecnum +\newdimen\tocsectotal +\newdimen\tocsubsecnum +\newdimen\tocsubsectotal +\newdimen\tocsubsubsecnum +\newdimen\tocsubsubsectotal +\newdimen\tocparanum +\newdimen\tocparatotal +\newdimen\tocsubparanum +\tocchpnum=\z@ % no chapter numbers +\tocsecnum=15\p@ % section 88. plus 2.222pt +\tocsubsecnum=23\p@ % subsection 88.8 plus 2.222pt +\tocsubsubsecnum=27\p@ % subsubsection 88.8.8 plus 1.444pt +\tocparanum=35\p@ % paragraph 88.8.8.8 plus 1.666pt +\tocsubparanum=43\p@ % subparagraph 88.8.8.8.8 plus 1.888pt +\def\calctocindent{% +\tocsectotal=\tocchpnum +\advance\tocsectotal by\tocsecnum +\tocsubsectotal=\tocsectotal +\advance\tocsubsectotal by\tocsubsecnum +\tocsubsubsectotal=\tocsubsectotal +\advance\tocsubsubsectotal by\tocsubsubsecnum +\tocparatotal=\tocsubsubsectotal +\advance\tocparatotal by\tocparanum} +\calctocindent + +\def\l@section{\@dottedtocline{1}{\tocchpnum}{\tocsecnum}} +\def\l@subsection{\@dottedtocline{2}{\tocsectotal}{\tocsubsecnum}} +\def\l@subsubsection{\@dottedtocline{3}{\tocsubsectotal}{\tocsubsubsecnum}} +\def\l@paragraph{\@dottedtocline{4}{\tocsubsubsectotal}{\tocparanum}} +\def\l@subparagraph{\@dottedtocline{5}{\tocparatotal}{\tocsubparanum}} + +\def\listoffigures{\@restonecolfalse\if@twocolumn\@restonecoltrue\onecolumn + \fi\section*{\listfigurename\@mkboth{{\listfigurename}}{{\listfigurename}}} + \@starttoc{lof}\if@restonecol\twocolumn\fi} +\def\l@figure{\@dottedtocline{1}{0em}{1.5em}} + +\def\listoftables{\@restonecolfalse\if@twocolumn\@restonecoltrue\onecolumn + \fi\section*{\listtablename\@mkboth{{\listtablename}}{{\listtablename}}} + \@starttoc{lot}\if@restonecol\twocolumn\fi} +\let\l@table\l@figure + +\renewcommand\listoffigures{% + \section*{\listfigurename + \@mkboth{\listfigurename}{\listfigurename}}% + \@starttoc{lof}% + } + +\renewcommand\listoftables{% + \section*{\listtablename + \@mkboth{\listtablename}{\listtablename}}% + \@starttoc{lot}% + } + +\ifx\oribibl\undefined +\ifx\citeauthoryear\undefined +\renewenvironment{thebibliography}[1] + {\section*{\refname} + \def\@biblabel##1{##1.} + \small + \list{\@biblabel{\@arabic\c@enumiv}}% + {\settowidth\labelwidth{\@biblabel{#1}}% + \leftmargin\labelwidth + \advance\leftmargin\labelsep + \if@openbib + \advance\leftmargin\bibindent + \itemindent -\bibindent + \listparindent \itemindent + \parsep \z@ + \fi + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{\@arabic\c@enumiv}}% + \if@openbib + \renewcommand\newblock{\par}% + \else + \renewcommand\newblock{\hskip .11em \@plus.33em \@minus.07em}% + \fi + \sloppy\clubpenalty4000\widowpenalty4000% + \sfcode`\.=\@m} + {\def\@noitemerr + {\@latex@warning{Empty `thebibliography' environment}}% + \endlist} +\def\@lbibitem[#1]#2{\item[{[#1]}\hfill]\if@filesw + {\let\protect\noexpand\immediate + \write\@auxout{\string\bibcite{#2}{#1}}}\fi\ignorespaces} +\newcount\@tempcntc +\def\@citex[#1]#2{\if@filesw\immediate\write\@auxout{\string\citation{#2}}\fi + \@tempcnta\z@\@tempcntb\m@ne\def\@citea{}\@cite{\@for\@citeb:=#2\do + {\@ifundefined + {b@\@citeb}{\@citeo\@tempcntb\m@ne\@citea\def\@citea{,}{\bfseries + ?}\@warning + {Citation `\@citeb' on page \thepage \space undefined}}% + {\setbox\z@\hbox{\global\@tempcntc0\csname b@\@citeb\endcsname\relax}% + \ifnum\@tempcntc=\z@ \@citeo\@tempcntb\m@ne + \@citea\def\@citea{,}\hbox{\csname b@\@citeb\endcsname}% + \else + \advance\@tempcntb\@ne + \ifnum\@tempcntb=\@tempcntc + \else\advance\@tempcntb\m@ne\@citeo + \@tempcnta\@tempcntc\@tempcntb\@tempcntc\fi\fi}}\@citeo}{#1}} +\def\@citeo{\ifnum\@tempcnta>\@tempcntb\else + \@citea\def\@citea{,\,\hskip\z@skip}% + \ifnum\@tempcnta=\@tempcntb\the\@tempcnta\else + {\advance\@tempcnta\@ne\ifnum\@tempcnta=\@tempcntb \else + \def\@citea{--}\fi + \advance\@tempcnta\m@ne\the\@tempcnta\@citea\the\@tempcntb}\fi\fi} +\else +\renewenvironment{thebibliography}[1] + {\section*{\refname} + \small + \list{}% + {\settowidth\labelwidth{}% + \leftmargin\parindent + \itemindent=-\parindent + \labelsep=\z@ + \if@openbib + \advance\leftmargin\bibindent + \itemindent -\bibindent + \listparindent \itemindent + \parsep \z@ + \fi + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{}}% + \if@openbib + \renewcommand\newblock{\par}% + \else + \renewcommand\newblock{\hskip .11em \@plus.33em \@minus.07em}% + \fi + \sloppy\clubpenalty4000\widowpenalty4000% + \sfcode`\.=\@m} + {\def\@noitemerr + {\@latex@warning{Empty `thebibliography' environment}}% + \endlist} + \def\@cite#1{#1}% + \def\@lbibitem[#1]#2{\item[]\if@filesw + {\def\protect##1{\string ##1\space}\immediate + \write\@auxout{\string\bibcite{#2}{#1}}}\fi\ignorespaces} + \fi +\else +\@cons\@openbib@code{\noexpand\small} +\fi + +\def\idxquad{\hskip 10\p@}% space that divides entry from number + +\def\@idxitem{\par\hangindent 10\p@} + +\def\subitem{\par\setbox0=\hbox{--\enspace}% second order + \noindent\hangindent\wd0\box0}% index entry + +\def\subsubitem{\par\setbox0=\hbox{--\,--\enspace}% third + \noindent\hangindent\wd0\box0}% order index entry + +\def\indexspace{\par \vskip 10\p@ plus5\p@ minus3\p@\relax} + +\renewenvironment{theindex} + {\@mkboth{\indexname}{\indexname}% + \thispagestyle{empty}\parindent\z@ + \parskip\z@ \@plus .3\p@\relax + \let\item\par + \def\,{\relax\ifmmode\mskip\thinmuskip + \else\hskip0.2em\ignorespaces\fi}% + \normalfont\small + \begin{multicols}{2}[\@makeschapterhead{\indexname}]% + } + {\end{multicols}} + +\renewcommand\footnoterule{% + \kern-3\p@ + \hrule\@width 2truecm + \kern2.6\p@} + \newdimen\fnindent + \fnindent1em +\long\def\@makefntext#1{% + \parindent \fnindent% + \leftskip \fnindent% + \noindent + \llap{\hb@xt@1em{\hss\@makefnmark\ }}\ignorespaces#1} + +\long\def\@makecaption#1#2{% + \small + \vskip\abovecaptionskip + \sbox\@tempboxa{{\bfseries #1.} #2}% + \ifdim \wd\@tempboxa >\hsize + {\bfseries #1.} #2\par + \else + \global \@minipagefalse + \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}% + \fi + \vskip\belowcaptionskip} + +\def\fps@figure{htbp} +\def\fnum@figure{\figurename\thinspace\thefigure} +\def \@floatboxreset {% + \reset@font + \small + \@setnobreak + \@setminipage +} +\def\fps@table{htbp} +\def\fnum@table{\tablename~\thetable} +\renewenvironment{table} + {\setlength\abovecaptionskip{0\p@}% + \setlength\belowcaptionskip{10\p@}% + \@float{table}} + {\end@float} +\renewenvironment{table*} + {\setlength\abovecaptionskip{0\p@}% + \setlength\belowcaptionskip{10\p@}% + \@dblfloat{table}} + {\end@dblfloat} + +\long\def\@caption#1[#2]#3{\par\addcontentsline{\csname + ext@#1\endcsname}{#1}{\protect\numberline{\csname + the#1\endcsname}{\ignorespaces #2}}\begingroup + \@parboxrestore + \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par + \endgroup} + +% LaTeX does not provide a command to enter the authors institute +% addresses. The \institute command is defined here. + +\newcounter{@inst} +\newcounter{@auth} +\newcounter{auco} +\newdimen\instindent +\newbox\authrun +\newtoks\authorrunning +\newtoks\tocauthor +\newbox\titrun +\newtoks\titlerunning +\newtoks\toctitle + +\def\clearheadinfo{\gdef\@author{No Author Given}% + \gdef\@title{No Title Given}% + \gdef\@subtitle{}% + \gdef\@institute{No Institute Given}% + \gdef\@thanks{}% + \global\titlerunning={}\global\authorrunning={}% + \global\toctitle={}\global\tocauthor={}} + +\def\institute#1{\gdef\@institute{#1}} + +\def\institutename{\par + \begingroup + \parskip=\z@ + \parindent=\z@ + \setcounter{@inst}{1}% + \def\and{\par\stepcounter{@inst}% + \noindent$^{\the@inst}$\enspace\ignorespaces}% + \setbox0=\vbox{\def\thanks##1{}\@institute}% + \ifnum\c@@inst=1\relax + \gdef\fnnstart{0}% + \else + \xdef\fnnstart{\c@@inst}% + \setcounter{@inst}{1}% + \noindent$^{\the@inst}$\enspace + \fi + \ignorespaces + \@institute\par + \endgroup} + +\def\@fnsymbol#1{\ensuremath{\ifcase#1\or\star\or{\star\star}\or + {\star\star\star}\or \dagger\or \ddagger\or + \mathchar "278\or \mathchar "27B\or \|\or **\or \dagger\dagger + \or \ddagger\ddagger \else\@ctrerr\fi}} + +\def\inst#1{\unskip$^{#1}$} +\def\fnmsep{\unskip$^,$} +\def\email#1{{\tt#1}} +\AtBeginDocument{\@ifundefined{url}{\def\url#1{#1}}{}% +\@ifpackageloaded{babel}{% +\@ifundefined{extrasenglish}{}{\addto\extrasenglish{\switcht@albion}}% +\@ifundefined{extrasfrenchb}{}{\addto\extrasfrenchb{\switcht@francais}}% +\@ifundefined{extrasgerman}{}{\addto\extrasgerman{\switcht@deutsch}}% +}{\switcht@@therlang}% +\providecommand{\keywords}[1]{\par\addvspace\baselineskip +\noindent\keywordname\enspace\ignorespaces#1}% +} +\def\homedir{\~{ }} + +\def\subtitle#1{\gdef\@subtitle{#1}} +\clearheadinfo +% +%%% to avoid hyperref warnings +\providecommand*{\toclevel@author}{999} +%%% to make title-entry parent of section-entries +\providecommand*{\toclevel@title}{0} +% +\renewcommand\maketitle{\newpage +\phantomsection + \refstepcounter{chapter}% + \stepcounter{section}% + \setcounter{section}{0}% + \setcounter{subsection}{0}% + \setcounter{figure}{0} + \setcounter{table}{0} + \setcounter{equation}{0} + \setcounter{footnote}{0}% + \begingroup + \parindent=\z@ + \renewcommand\thefootnote{\@fnsymbol\c@footnote}% + \if@twocolumn + \ifnum \col@number=\@ne + \@maketitle + \else + \twocolumn[\@maketitle]% + \fi + \else + \newpage + \global\@topnum\z@ % Prevents figures from going at top of page. + \@maketitle + \fi + \thispagestyle{empty}\@thanks +% + \def\\{\unskip\ \ignorespaces}\def\inst##1{\unskip{}}% + \def\thanks##1{\unskip{}}\def\fnmsep{\unskip}% + \instindent=\hsize + \advance\instindent by-\headlineindent + \if!\the\toctitle!\addcontentsline{toc}{title}{\@title}\else + \addcontentsline{toc}{title}{\the\toctitle}\fi + \if@runhead + \if!\the\titlerunning!\else + \edef\@title{\the\titlerunning}% + \fi + \global\setbox\titrun=\hbox{\small\rm\unboldmath\ignorespaces\@title}% + \ifdim\wd\titrun>\instindent + \typeout{Title too long for running head. Please supply}% + \typeout{a shorter form with \string\titlerunning\space prior to + \string\maketitle}% + \global\setbox\titrun=\hbox{\small\rm + Title Suppressed Due to Excessive Length}% + \fi + \xdef\@title{\copy\titrun}% + \fi +% + \if!\the\tocauthor!\relax + {\def\and{\noexpand\protect\noexpand\and}% + \protected@xdef\toc@uthor{\@author}}% + \else + \def\\{\noexpand\protect\noexpand\newline}% + \protected@xdef\scratch{\the\tocauthor}% + \protected@xdef\toc@uthor{\scratch}% + \fi + \addtocontents{toc}{\noexpand\protect\noexpand\authcount{\the\c@auco}}% + \addcontentsline{toc}{author}{\toc@uthor}% + \if@runhead + \if!\the\authorrunning! + \value{@inst}=\value{@auth}% + \setcounter{@auth}{1}% + \else + \edef\@author{\the\authorrunning}% + \fi + \global\setbox\authrun=\hbox{\small\unboldmath\@author\unskip}% + \ifdim\wd\authrun>\instindent + \typeout{Names of authors too long for running head. Please supply}% + \typeout{a shorter form with \string\authorrunning\space prior to + \string\maketitle}% + \global\setbox\authrun=\hbox{\small\rm + Authors Suppressed Due to Excessive Length}% + \fi + \xdef\@author{\copy\authrun}% + \markboth{\@author}{\@title}% + \fi + \endgroup + \setcounter{footnote}{\fnnstart}% + \clearheadinfo} +% +\def\@maketitle{\newpage + \markboth{}{}% + \def\lastand{\ifnum\value{@inst}=2\relax + \unskip{} \andname\ + \else + \unskip \lastandname\ + \fi}% + \def\and{\stepcounter{@auth}\relax + \ifnum\value{@auth}=\value{@inst}% + \lastand + \else + \unskip, + \fi}% + \begin{center}% + \let\newline\\ + {\Large \bfseries\boldmath + \pretolerance=10000 + \@title \par}\vskip .8cm +\if!\@subtitle!\else {\large \bfseries\boldmath + \vskip -.65cm + \pretolerance=10000 + \@subtitle \par}\vskip .8cm\fi + \setbox0=\vbox{\setcounter{@auth}{1}\def\and{\stepcounter{@auth}}% + \def\thanks##1{}\@author}% + \global\value{@inst}=\value{@auth}% + \global\value{auco}=\value{@auth}% + \setcounter{@auth}{1}% +{\lineskip .5em +\noindent\ignorespaces +\@author\vskip.35cm} + {\small\institutename} + \end{center}% + } + +% definition of the "\spnewtheorem" command. +% +% Usage: +% +% \spnewtheorem{env_nam}{caption}[within]{cap_font}{body_font} +% or \spnewtheorem{env_nam}[numbered_like]{caption}{cap_font}{body_font} +% or \spnewtheorem*{env_nam}{caption}{cap_font}{body_font} +% +% New is "cap_font" and "body_font". It stands for +% fontdefinition of the caption and the text itself. +% +% "\spnewtheorem*" gives a theorem without number. +% +% A defined spnewthoerem environment is used as described +% by Lamport. +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def\@thmcountersep{} +\def\@thmcounterend{.} + +\def\spnewtheorem{\@ifstar{\@sthm}{\@Sthm}} + +% definition of \spnewtheorem with number + +\def\@spnthm#1#2{% + \@ifnextchar[{\@spxnthm{#1}{#2}}{\@spynthm{#1}{#2}}} +\def\@Sthm#1{\@ifnextchar[{\@spothm{#1}}{\@spnthm{#1}}} + +\def\@spxnthm#1#2[#3]#4#5{\expandafter\@ifdefinable\csname #1\endcsname + {\@definecounter{#1}\@addtoreset{#1}{#3}% + \expandafter\xdef\csname the#1\endcsname{\expandafter\noexpand + \csname the#3\endcsname \noexpand\@thmcountersep \@thmcounter{#1}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#4}{#5}}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@spynthm#1#2#3#4{\expandafter\@ifdefinable\csname #1\endcsname + {\@definecounter{#1}% + \expandafter\xdef\csname the#1\endcsname{\@thmcounter{#1}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#3}{#4}}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@spothm#1[#2]#3#4#5{% + \@ifundefined{c@#2}{\@latexerr{No theorem environment `#2' defined}\@eha}% + {\expandafter\@ifdefinable\csname #1\endcsname + {\newaliascnt{#1}{#2}% + \expandafter\xdef\csname #1name\endcsname{#3}% + \global\@namedef{#1}{\@spthm{#1}{\csname #1name\endcsname}{#4}{#5}}% + \global\@namedef{end#1}{\@endtheorem}}}} + +\def\@spthm#1#2#3#4{\topsep 7\p@ \@plus2\p@ \@minus4\p@ +\refstepcounter{#1}% +\@ifnextchar[{\@spythm{#1}{#2}{#3}{#4}}{\@spxthm{#1}{#2}{#3}{#4}}} + +\def\@spxthm#1#2#3#4{\@spbegintheorem{#2}{\csname the#1\endcsname}{#3}{#4}% + \ignorespaces} + +\def\@spythm#1#2#3#4[#5]{\@spopargbegintheorem{#2}{\csname + the#1\endcsname}{#5}{#3}{#4}\ignorespaces} + +\def\@spbegintheorem#1#2#3#4{\trivlist + \item[\hskip\labelsep{#3#1\ #2\@thmcounterend}]#4} + +\def\@spopargbegintheorem#1#2#3#4#5{\trivlist + \item[\hskip\labelsep{#4#1\ #2}]{#4(#3)\@thmcounterend\ }#5} + +% definition of \spnewtheorem* without number + +\def\@sthm#1#2{\@Ynthm{#1}{#2}} + +\def\@Ynthm#1#2#3#4{\expandafter\@ifdefinable\csname #1\endcsname + {\global\@namedef{#1}{\@Thm{\csname #1name\endcsname}{#3}{#4}}% + \expandafter\xdef\csname #1name\endcsname{#2}% + \global\@namedef{end#1}{\@endtheorem}}} + +\def\@Thm#1#2#3{\topsep 7\p@ \@plus2\p@ \@minus4\p@ +\@ifnextchar[{\@Ythm{#1}{#2}{#3}}{\@Xthm{#1}{#2}{#3}}} + +\def\@Xthm#1#2#3{\@Begintheorem{#1}{#2}{#3}\ignorespaces} + +\def\@Ythm#1#2#3[#4]{\@Opargbegintheorem{#1} + {#4}{#2}{#3}\ignorespaces} + +\def\@Begintheorem#1#2#3{#3\trivlist + \item[\hskip\labelsep{#2#1\@thmcounterend}]} + +\def\@Opargbegintheorem#1#2#3#4{#4\trivlist + \item[\hskip\labelsep{#3#1}]{#3(#2)\@thmcounterend\ }} + +\if@envcntsect + \def\@thmcountersep{.} + \spnewtheorem{theorem}{Theorem}[section]{\bfseries}{\itshape} +\else + \spnewtheorem{theorem}{Theorem}{\bfseries}{\itshape} + \if@envcntreset + \@addtoreset{theorem}{section} + \else + \@addtoreset{theorem}{chapter} + \fi +\fi + +%definition of divers theorem environments +\spnewtheorem*{claim}{Claim}{\itshape}{\rmfamily} +\spnewtheorem*{proof}{Proof}{\itshape}{\rmfamily} +\if@envcntsame % alle Umgebungen wie Theorem. + \def\spn@wtheorem#1#2#3#4{\@spothm{#1}[theorem]{#2}{#3}{#4}} +\else % alle Umgebungen mit eigenem Zaehler + \if@envcntsect % mit section numeriert + \def\spn@wtheorem#1#2#3#4{\@spxnthm{#1}{#2}[section]{#3}{#4}} + \else % nicht mit section numeriert + \if@envcntreset + \def\spn@wtheorem#1#2#3#4{\@spynthm{#1}{#2}{#3}{#4} + \@addtoreset{#1}{section}} + \else + \def\spn@wtheorem#1#2#3#4{\@spynthm{#1}{#2}{#3}{#4} + \@addtoreset{#1}{chapter}}% + \fi + \fi +\fi +\spn@wtheorem{case}{Case}{\itshape}{\rmfamily} +\spn@wtheorem{conjecture}{Conjecture}{\itshape}{\rmfamily} +\spn@wtheorem{corollary}{Corollary}{\bfseries}{\itshape} +\spn@wtheorem{definition}{Definition}{\bfseries}{\itshape} +\spn@wtheorem{example}{Example}{\itshape}{\rmfamily} +\spn@wtheorem{exercise}{Exercise}{\itshape}{\rmfamily} +\spn@wtheorem{lemma}{Lemma}{\bfseries}{\itshape} +\spn@wtheorem{note}{Note}{\itshape}{\rmfamily} +\spn@wtheorem{problem}{Problem}{\itshape}{\rmfamily} +\spn@wtheorem{property}{Property}{\itshape}{\rmfamily} +\spn@wtheorem{proposition}{Proposition}{\bfseries}{\itshape} +\spn@wtheorem{question}{Question}{\itshape}{\rmfamily} +\spn@wtheorem{solution}{Solution}{\itshape}{\rmfamily} +\spn@wtheorem{remark}{Remark}{\itshape}{\rmfamily} + +\def\@takefromreset#1#2{% + \def\@tempa{#1}% + \let\@tempd\@elt + \def\@elt##1{% + \def\@tempb{##1}% + \ifx\@tempa\@tempb\else + \@addtoreset{##1}{#2}% + \fi}% + \expandafter\expandafter\let\expandafter\@tempc\csname cl@#2\endcsname + \expandafter\def\csname cl@#2\endcsname{}% + \@tempc + \let\@elt\@tempd} + +\def\theopargself{\def\@spopargbegintheorem##1##2##3##4##5{\trivlist + \item[\hskip\labelsep{##4##1\ ##2}]{##4##3\@thmcounterend\ }##5} + \def\@Opargbegintheorem##1##2##3##4{##4\trivlist + \item[\hskip\labelsep{##3##1}]{##3##2\@thmcounterend\ }} + } + +\renewenvironment{abstract}{% + \list{}{\advance\topsep by0.35cm\relax\small + \leftmargin=1cm + \labelwidth=\z@ + \listparindent=\z@ + \itemindent\listparindent + \rightmargin\leftmargin}\item[\hskip\labelsep + \bfseries\abstractname]} + {\endlist} + +\newdimen\headlineindent % dimension for space between +\headlineindent=1.166cm % number and text of headings. + +\def\ps@headings{\let\@mkboth\@gobbletwo + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\normalfont\small\rlap{\thepage}\hspace{\headlineindent}% + \leftmark\hfil} + \def\@oddhead{\normalfont\small\hfil\rightmark\hspace{\headlineindent}% + \llap{\thepage}} + \def\chaptermark##1{}% + \def\sectionmark##1{}% + \def\subsectionmark##1{}} + +\def\ps@titlepage{\let\@mkboth\@gobbletwo + \let\@oddfoot\@empty\let\@evenfoot\@empty + \def\@evenhead{\normalfont\small\rlap{\thepage}\hspace{\headlineindent}% + \hfil} + \def\@oddhead{\normalfont\small\hfil\hspace{\headlineindent}% + \llap{\thepage}} + \def\chaptermark##1{}% + \def\sectionmark##1{}% + \def\subsectionmark##1{}} + +\if@runhead\ps@headings\else +\ps@empty\fi + +\setlength\arraycolsep{1.4\p@} +\setlength\tabcolsep{1.4\p@} + +\endinput +%end of file llncs.cls \ No newline at end of file diff --git a/docs/zerojoin/main.bib b/docs/zerojoin/main.bib new file mode 100644 index 0000000000..7f2b648736 --- /dev/null +++ b/docs/zerojoin/main.bib @@ -0,0 +1,1661 @@ +@InProceedings{multistage, +author="Chepurnoy, Alexander +and Saxena, Amitabh", +editor="P{\'e}rez-Sol{\`a}, Cristina +and Navarro-Arribas, Guillermo +and Biryukov, Alex +and Garcia-Alfaro, Joaquin", +title="Multi-stage Contracts in the UTXO Model", +booktitle="Data Privacy Management, Cryptocurrencies and Blockchain Technology", +year="2019", +publisher="Springer International Publishing", +address="Cham", +pages="244--254", +abstract="Smart contract platforms such as Bitcoin and Ethereum allow writing programs that run on a decentralized computer. Bitcoin uses short-lived immutable data structures called UTXOs for data manipulation. Ethereum, on the other hand uses, long-lived mutable data structures called accounts. UTXOs are easier to handle, less error prone and scale better because the only operation we can do with them is to create or destroy (i.e., spend) them. The code inside a UTXO is executed only once, when it is spent. Additionally, this code refers to only local context (i.e., it is stateless). In Ethereum's account based system, there is a shared global context which each account can access and modify, thereby causing side affects. However, the benefit of persistent storage offered by accounts makes up for these drawbacks. In this work, we describe how to emulate persistent storage in UTXO based systems using a technique called transaction trees. This allows us to emulate the functionality of account-based systems such as Ethereum without the overhead of accounts. We demonstrate this via several examples which include contracts for a Rock-Paper-Scissors game, crowdfunding and an initial coin offering (ICO). The contracts are created in a UTXO based smart contract platform called Ergo that supports transaction trees.", +isbn="978-3-030-31500-9" +} + +@misc{ergo, + author={Ergo Developers}, + year={2019}, + title={Ergo: A Resilient Platform For Contractual Money}, + howpublished={\url{https://ergoplatform.org/docs/whitepaper.pdf}} +} +@misc{ergomix-vuln, + author={Jason Davies}, + year={2020}, + title={ErgoMix Vulnerability}, + howpublished={\url{https://blog.plutomonkey.com/2020/04/ergomix-vulnerability/}} +} +@article{selfish, + author = {Ittay Eyal and + Emin G{\"{u}}n Sirer}, + title = {Majority is not Enough: Bitcoin Mining is Vulnerable}, + journal = {CoRR}, + volume = {abs/1311.0243}, + year = {2013}, + url = {http://arxiv.org/abs/1311.0243}, + archivePrefix = {arXiv}, + eprint = {1311.0243}, + timestamp = {Mon, 13 Aug 2018 16:46:18 +0200}, + biburl = {https://dblp.org/rec/bib/journals/corr/EyalS13}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +@Misc{Hearn:2012:BIP, + author = "Mike Hearn and Matt Corallo", + title = "{Bitcoin} improvement proposal 0037", + howpublished = "Web document", + month = oct, + year = "2012", + bibdate = "Tue Jan 23 11:11:54 2018", + bibsource = "http://www.math.utah.edu/pub/tex/bib/bitcoin.bib", + URL = "https://en.bitcoin.it/wiki/BIP_0037", + acknowledgement = ack-nhfb, +} +@misc{goldfinger, + author={McNelis, Paul D.}, + year={2017}, + title={We need a financial James Bond to prevent a ‘Goldfinger’ attack on Bitcoin}, + howpublished={\url{https://www.americamagazine.org/politics-society/2017/10/31/we-need-financial-james-bond-prevent-goldfinger-attack-bitcoin}} +} + +@misc{SSCMD92, + author={{SecureSCM} project}, + year={2009}, + title={{WP9} (Cryptographic Aspects), Deliverable {D9.2} (Security Analysis)}, + howpublished={\url{https://pdfs.semanticscholar.org/85d4/2bc7eb03c4ccffffd6610cb74da347976ff9.pdf}} +} + +@inproceedings{miller2015nonoutsourceable, + title={Nonoutsourceable scratch-off puzzles to discourage bitcoin mining coalitions}, + author={Miller, Andrew and Kosba, Ahmed and Katz, Jonathan and Shi, Elaine}, + booktitle={Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security}, + pages={680--691}, + year={2015}, + organization={ACM} +} +@article{shamir1979share, + title={How to share a secret}, + author={Shamir, Adi}, + journal={Communications of the ACM}, + volume={22}, + number={11}, + pages={612--613}, + year={1979}, + publisher={ACm} +} +@misc{autolykos, + year = {2019}, + month = {03}, + title = {Autolykos: The Ergo Platform PoW Puzzle}, + howpublished = {\url{https://docs.ergoplatform.com/ErgoPow.pdf}} +} +@inproceedings{daian2017short, + title={(Short paper) Piecework: Generalized outsourcing control for proofs of work}, + author={Daian, Philip and Eyal, Ittay and Juels, Ari and Sirer, Emin G{\"u}n}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={182--190}, + year={2017}, + organization={Springer} +} + +@misc{fee-emission, + year={2019}, + month = {09}, + title={Paying fee in ErgoMix in primary tokens}, + howpublished={\url{https://www.ergoforum.org/t/paying-fee-in-ergomix-in-primary-tokens/73}} +} + +@misc{twophase, + year = {2014}, + month = {06}, + title = {How to Disincentivize Large Bitcoin Mining Pools}, + howpublished = {\url{http://hackingdistributed.com/2014/06/18/how-to-disincentivize-large-bitcoin-mining-pools/}} +} + +@book{Antonopoulos:2014:MBU:2695500, + author = {Antonopoulos, Andreas M.}, + title = {Mastering Bitcoin: Unlocking Digital Crypto-Currencies}, + year = {2014}, + isbn = {1449374042, 9781449374044}, + edition = {1st}, + publisher = {O'Reilly Media, Inc.}, +} + + +@article{kiayias2017non, + title={Non-Interactive Proofs of Proof-of-Work.}, + author={Kiayias, Aggelos and Miller, Andrew and Zindros, Dionysis} +} + +@misc{erc20, + year = {2019}, + author = {The Bitcoin Wiki}, + title = {Secp256k1}, + howpublished = {\url{https://en.bitcoin.it/wiki/Secp256k1}} +} +@misc{rfc7693, + series = {Request for Comments}, + number = 7693, + howpublished = {RFC 7693}, + publisher = {RFC Editor}, + doi = {10.17487/RFC7693}, + url = {https://rfc-editor.org/rfc/rfc7693.txt}, + author = {Markku-Juhani O. Saarinen and Jean-Philippe Aumasson}, + title = {{The BLAKE2 Cryptographic Hash and Message Authentication Code (MAC)}}, + pagetotal = 30, + year = 2015, + month = nov, + abstract = {This document describes the cryptographic hash function BLAKE2 and makes the algorithm specification and C source code conveniently available to the Internet community. BLAKE2 comes in two main flavors: BLAKE2b is optimized for 64-bit platforms and BLAKE2s for smaller architectures. BLAKE2 can be directly keyed, making it functionally equivalent to a Message Authentication Code (MAC).}, +} + +@InProceedings{RMCI17, +author="Reyzin, Leonid +and Meshkov, Dmitry +and Chepurnoy, Alexander +and Ivanov, Sasha", +editor="Kiayias, Aggelos", +title="Improving Authenticated Dynamic Dictionaries, with Applications to Cryptocurrencies", +booktitle="Financial Cryptography and Data Security", +year="2017", +publisher="Springer International Publishing", +address="Cham", +pages="376--392", +abstract="We improve the design and implementation of two-party and three-party authenticated dynamic dictionaries and apply these dictionaries to cryptocurrency ledgers.", +isbn="978-3-319-70972-7" +} +@article{Cook_2009, + title={A Concrete View of Rule 110 Computation}, + volume={1}, + ISSN={2075-2180}, + url={http://dx.doi.org/10.4204/EPTCS.1.4}, + DOI={10.4204/eptcs.1.4}, + journal={Electronic Proceedings in Theoretical Computer Science}, + publisher={Open Publishing Association}, + author={Cook, Matthew}, + year={2009}, + month={Jun}, + pages={31-55} +} + +@incollection{rendell2016turing, + title={Turing Machine in Conway Game of Life}, + author={Rendell, Paul}, + booktitle={Designing Beauty: The Art of Cellular Automata}, + pages={149--154}, + year={2016}, + publisher={Springer} +} + +@phdthesis{rendell2014turing, + title={Turing machine universality of the game of life}, + author={Rendell, Paul}, + year={2014}, + school={University of the West of England} +}} + +@inproceedings{neary2006p, + title={P-completeness of cellular automaton Rule 110}, + author={Neary, Turlough and Woods, Damien}, + booktitle={International Colloquium on Automata, Languages, and Programming}, + pages={132--143}, + year={2006}, + organization={Springer} +} + +@inproceedings{chepurnoy2018systematic, + title={A systematic approach to cryptocurrency fees}, + author={Chepurnoy, Alexander and Kharin, Vasily and Meshkov, Dmitry}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={19--30}, + year={2018}, + organization={Springer} +} + +@inproceedings{Luu:2016:MSC:2976749.2978309, + author = {Luu, Loi and Chu, Duc-Hiep and Olickel, Hrishi and Saxena, Prateek and Hobor, Aquinas}, + title = {Making Smart Contracts Smarter}, + booktitle = {Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security}, + series = {CCS '16}, + year = {2016}, + isbn = {978-1-4503-4139-4}, + location = {Vienna, Austria}, + pages = {254--269}, + numpages = {16}, + url = {http://doi.acm.org/10.1145/2976749.2978309}, + doi = {10.1145/2976749.2978309}, + acmid = {2978309}, + publisher = {ACM}, + address = {New York, NY, USA}, + keywords = {blockchain, cryptocurrencies, ethereum, smart contract, symbolic execution}, +} + +@article{szabo1997idea, + title={The idea of smart contracts}, + author={Szabo, Nick}, + journal={Nick Szabo's Papers and Concise Tutorials}, + volume={6}, + year={1997} +} + +@InProceedings{CKM18a, +author="Chepurnoy, Alexander +and Kharin, Vasily +and Meshkov, Dmitry", +editor="Garcia-Alfaro, Joaquin +and Herrera-Joancomart{\'i}, Jordi +and Livraga, Giovanni +and Rios, Ruben", +title="Self-reproducing Coins as Universal Turing Machine", +booktitle="Data Privacy Management, Cryptocurrencies and Blockchain Technology", +year="2018", +publisher="Springer International Publishing", +address="Cham", +pages="57--64", +abstract="Turing-completeness of smart contract languages in blockchain systems is often associated with a variety of language features (such as loops). On the contrary, we show that Turing-completeness of a blockchain system can be achieved through unwinding the recursive calls between multiple transactions and blocks instead of using a single one. We prove it by constructing a simple universal Turing machine using a small set of language features in the unspent transaction output (UTXO) model, with explicitly given relations between input and output transaction states. Neither unbounded loops nor possibly infinite validation time are needed in this approach.", +isbn="978-3-030-00305-0" +} + + +@InProceedings{fiatshamir, +author="Fiat, Amos +and Shamir, Adi", +editor="Odlyzko, Andrew M.", +title="How To Prove Yourself: Practical Solutions to Identification and Signature Problems", +booktitle="Advances in Cryptology --- CRYPTO' 86", +year="1987", +publisher="Springer Berlin Heidelberg", +address="Berlin, Heidelberg", +pages="186--194", +abstract="In this paper we describe simple identification and signature schemes which enable any user to prove his identity and the authenticity of his messages to any other user without shared or public keys. The schemes are provably secure against any known or chosen message attack if factoring is difficult, and typical implementations require only 1{\%} to 4{\%} of the number of modular multiplications required by the RSA scheme. Due to their simplicity, security and speed, these schemes are ideally suited for microprocessor-based devices such as smart cards, personal computers, and remote control systems.", +isbn="978-3-540-47721-1" +} +@inproceedings{zcash, + author = {Sasson, Eli Ben and Chiesa, Alessandro and Garman, Christina and Green, Matthew and Miers, Ian and Tromer, Eran and Virza, Madars}, + title = {Zerocash: Decentralized Anonymous Payments from Bitcoin}, + booktitle = {Proceedings of the 2014 IEEE Symposium on Security and Privacy}, + series = {SP '14}, + year = {2014}, + isbn = {978-1-4799-4686-0}, + pages = {459--474}, + numpages = {16}, + url = {https://doi.org/10.1109/SP.2014.36}, + doi = {10.1109/SP.2014.36}, + acmid = {2650810}, + publisher = {IEEE Computer Society}, + address = {Washington, DC, USA}, + keywords = {Bitcoin, decentralized electronic cash, zero knowledge}, +} +@misc{ct, + year = {2015}, + author = {Gregory Maxwell}, + title = {Confidential Transactions}, + howpublished = {\url{https://people.xiph.org/~greg/confidential_values.txt}} +} +@misc{erc20, + year = {2018}, + author = {The Ethereum Wiki}, + title = {ERC20 Token Standard}, + howpublished = {\url{https://theethereum.wiki/w/index.php/ERC20_Token_Standard}} +} +@misc{mw, + year = {2016}, + author = {T.E. Jedusor}, + title = {Mimblewimble}, + howpublished = {\url{https://download.wpsoftware.net/bitcoin/wizardry/mimblewimble.txt}} +} +@misc{zcash1, + year = {2016}, + title = {ZCash}, + howpublished = {\url{https://z.cash}} +} + +@misc{advtutorial, + year = {2019}, + month = {03}, + title = {Advanced ErgoScript Tutorial}, + howpublished = {\url{https://docs.ergoplatform.com/sigmastate_protocols.pdf}} +} +@misc{tutorial, + year = {2019}, + month = {03}, + title = {ErgoScript, a Cryptocurrency Scripting Language Supporting Noninteractive Zero-Knowledge Proofs}, + howpublished = {\url{https://docs.ergoplatform.com/ErgoScript.pdf}} +} + +@misc{ergomix-impl, + year = {2019}, + month = {10}, + title = {Lets Play with ErgoMix}, + howpublished = {\url{https://www.ergoforum.org/t/lets-play-with-ergomix/108}} +} + +@misc{raddress, + year = {2018}, + month = {06}, + title = {Adding anti-theft feature to Bitcoin using a new address type}, + howpublished = {\url{https://bitcointalk.org/index.php?topic=4440801.0}} +} + +@misc{coinjoin, + year = {2013}, + month = {08}, + title = {CoinJoin: Bitcoin privacy for the real world}, + howpublished = {\url{https://bitcointalk.org/?topic=279249}} +} + + +@misc{langrepo, + author = {Scorex Foundation}, + title = {Sigmastate Interpretter (ErgoScript)}, + year = {2017}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\url{https://github.com/ScorexFoundation/sigmastate-interpreter}}, + commit = {6d427a2323a4ac1ecc9b18bb0b00416f6165b226} +} +@inproceedings{zerocoin, + author = {Miers, Ian and Garman, Christina and Green, Matthew and Rubin, A.D.}, + year = {2013}, + month = {05}, + pages = {397-411}, + title = {Zerocoin: Anonymous Distributed E-Cash from Bitcoin}, + isbn = {978-1-4673-6166-8}, + booktitle = {Proceedings - IEEE Symposium on Security and Privacy}, + doi = {10.1109/SP.2013.34} +} +@inproceedings{NW06, + author = {Turlough Neary and + Damien Woods}, + title = {P-completeness of Cellular Automaton Rule 110}, + booktitle = {Automata, Languages and Programming, 33rd International Colloquium, + {ICALP} 2006, Venice, Italy, July 10-14, 2006, Proceedings, Part {I}}, + pages = {132--143}, + year = {2006}, + crossref = {DBLP:conf/icalp/2006-1}, + url = {https://doi.org/10.1007/11786986\_13}, + doi = {10.1007/11786986\_13}, + timestamp = {Fri, 02 Jun 2017 13:01:07 +0200}, + biburl = {https://dblp.org/rec/bib/conf/icalp/NearyW06}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +@proceedings{DBLP:conf/icalp/2006-1, + editor = {Michele Bugliesi and + Bart Preneel and + Vladimiro Sassone and + Ingo Wegener}, + title = {Automata, Languages and Programming, 33rd International Colloquium, + {ICALP} 2006, Venice, Italy, July 10-14, 2006, Proceedings, Part {I}}, + series = {Lecture Notes in Computer Science}, + volume = {4051}, + publisher = {Springer}, + year = {2006}, + url = {https://doi.org/10.1007/11786986}, + doi = {10.1007/11786986}, + isbn = {3-540-35904-4}, + timestamp = {Fri, 02 Jun 2017 13:01:07 +0200}, + biburl = {https://dblp.org/rec/bib/conf/icalp/2006-1}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +@book{wolfram1986theory, + title={Theory and applications of cellular automata: including selected + papers 1983-1986}, + author={Wolfram, Stephen}, + year={1986}, + publisher={World scientific} +} +@article{cook2004universality, + title={Universality in elementary cellular automata}, + author={Cook, Matthew}, + journal={Complex systems}, + volume={15}, + number={1}, + pages={1--40}, + year={2004}, + publisher={[Champaign, IL, USA: Complex Systems + Publications, Inc., c1987-} +} +@misc{Scilla, +title = {Scilla: a {S}mart {C}ontract {I}ntermediate-{L}evel {LA}nguage}, +author = {Ilya Sergey, Amrit Kumar, Aquinas Hobor}, +year = {2018}, +Eprint = {arXiv:1806.10116}, +note = {\url{https://arxiv.org/abs/1801.00687}}, +} +@misc{CKM18, +Author = {Alexander Chepurnoy and Vasily Kharin and Dmitry Meshkov}, +Title = {Self-Reproducing Coins as Universal Turing Machine}, +Year = {2018}, +Eprint = {arXiv:1806.10116}, +note = {\url{https://arxiv.org/abs/1806.10116}}, +} +@misc{Nol13, +author={Tier Nolan}, +title = {Alt chains and atomic transfers}, +url={https://bitcointalk.org/index.php?topic=193281.msg2224949#msg2224949}, +year = 2013 +} +@phdthesis{Cra96, +author = {Ronald Cramer}, +title = {Modular Design of Secure, yet Practical Cryptographic Protocols}, +school = {University of Amsterdam}, +year = 1996 +} +@article{wood2014ethereum, + title={Ethereum: A secure decentralised generalised transaction ledger}, + author={Wood, Gavin}, + journal={Ethereum project yellow paper}, + volume={151}, + pages={1--32}, + year={2014}, + howpublished = {\url{http://gavwood.com/Paper.pdf}} + + } + + +@article{rps15, + added-at = {2018-08-14T00:00:00.000+0200}, + author = {Delmolino, Kevin and Arnett, Mitchell and Kosba, Ahmed E. and Miller, Andrew and Shi, Elaine}, + biburl = {https://www.bibsonomy.org/bibtex/201346e267684bdc7cd96b186072bbf50/dblp}, + ee = {http://eprint.iacr.org/2015/460}, + interhash = {c694ac7b413b6acfd8c4d518df9ca33d}, + intrahash = {01346e267684bdc7cd96b186072bbf50}, + journal = {IACR Cryptology ePrint Archive}, + keywords = {dblp}, + pages = 460, + timestamp = {2018-08-15T11:53:36.000+0200}, + title = {Step by Step Towards Creating a Safe Smart Contract: Lessons and Insights from a Cryptocurrency Lab.}, + url = {http://dblp.uni-trier.de/db/journals/iacr/iacr2015.html#DelmolinoAKMS15}, + volume = 2015, + year = 2015 +} + + + +@misc{Dam10, +author = {Ivan Damg{\aa}rd}, +title = {{On $\Sigma$-Protocols}}, +year = 2010, +note = {\url{http://www.cs.au.dk/~ivan/Sigma.pdf}} +} + + +@book{HL10, +author= {Carmit Hazay and Yehuda Lindell}, +title = {Efficient Secure Two-Party Protocols: Techniques and Constructions}, +year = 2010, +publisher = {Springer} +} + + +@inproceedings{CL06, + author = {Melissa Chase and + Anna Lysyanskaya}, + title = {On Signatures of Knowledge}, + booktitle = {Advances in Cryptology - {CRYPTO} 2006, 26th Annual International + Cryptology Conference, Santa Barbara, California, USA, August 20-24, + 2006, Proceedings}, + pages = {78--96}, + year = {2006}, + crossref = {DBLP:conf/crypto/2006}, + url = {https://doi.org/10.1007/11818175\_5}, + doi = {10.1007/11818175\_5}, + timestamp = {Fri, 02 Jun 2017 13:01:07 +0200}, + biburl = {https://dblp.org/rec/bib/conf/crypto/ChaseL06}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +@proceedings{DBLP:conf/crypto/2006, + editor = {Cynthia Dwork}, + title = {Advances in Cryptology - {CRYPTO} 2006, 26th Annual International + Cryptology Conference, Santa Barbara, California, USA, August 20-24, + 2006, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {4117}, + publisher = {Springer}, + year = {2006}, + url = {https://doi.org/10.1007/11818175}, + doi = {10.1007/11818175}, + isbn = {3-540-37432-9}, + timestamp = {Fri, 02 Jun 2017 13:01:07 +0200}, + biburl = {https://dblp.org/rec/bib/conf/crypto/2006}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} + +@Article{Sch91, + author = "Claus-Peter Schnorr", + title = "Efficient Signature Generation by Smart Cards", + pages = "161--174", + volume = 4, + month = "", + publisher = "Springer", + year = 1991, + journal = "Journal of Cryptology", + number = 3, +} +@book{rfc8032, +author={S. Josefsson and I. Liusvaara}, +title={{RFC} 8032: Edwards-Curve Digital Signature Algorithm ({EdDSA})}, +publisher={IETF}, +year=2017, +note={\url{https://tools.ietf.org/html/rfc8032}}, +} +@inproceedings{CDS94, + author = {Ronald Cramer and + Ivan Damg{\aa}rd and + Berry Schoenmakers}, + title = {Proofs of Partial Knowledge and Simplified Design of Witness Hiding + Protocols}, + booktitle = {Advances in Cryptology - {CRYPTO} '94, 14th Annual International Cryptology + Conference, Santa Barbara, California, USA, August 21-25, 1994, Proceedings}, + pages = {174--187}, + year = {1994}, + crossref = {DBLP:conf/crypto/1994}, + url = {https://doi.org/10.1007/3-540-48658-5_19}, + doi = {10.1007/3-540-48658-5_19}, + timestamp = {Sat, 20 May 2017 15:32:52 +0200}, + biburl = {https://dblp.org/rec/bib/conf/crypto/CramerDS94}, + bibsource = {dblp computer science bibliography, https://dblp.org}, +note = {\url{http://www.win.tue.nl/~berry/papers/crypto94.pdf}} +} +@proceedings{DBLP:conf/crypto/1994, + editor = {Yvo Desmedt}, + title = {Advances in Cryptology - {CRYPTO} '94, 14th Annual International Cryptology + Conference, Santa Barbara, California, USA, August 21-25, 1994, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {839}, + publisher = {Springer}, + year = {1994}, + url = {https://doi.org/10.1007/3-540-48658-5}, + doi = {10.1007/3-540-48658-5}, + isbn = {3-540-58333-5}, + timestamp = {Sat, 20 May 2017 15:32:52 +0200}, + biburl = {https://dblp.org/rec/bib/conf/crypto/1994}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} +@techreport{ourPaperFull, + author = {Leonid Reyzin and + Dmitry Meshkov and + Alexander Chepurnoy and + Sasha Ivanov}, + title = {Improving Authenticated Dynamic Dictionaries, with Applications to + Cryptocurrencies}, + institution = {{IACR} Cryptology ePrint Archive}, + number = {2016/994}, + year = 2016, + note = {\url{http://eprint.iacr.org/2016/994}}, +} + +@misc{peterToddTrees, +title = {Making {UTXO} Set Growth Irrelevant With Low-Latency Delayed {TXO} Commitments}, +author = {Peter Todd}, +year = 2016, +note = {\url{https://petertodd.org/2016/delayed-txo-commitments}}, +} +@misc{tendermint, +author={Jae Kwon}, +title={Tendermint Go-Merkle}, +note={\url{https://github.com/tendermint/go-merkle}}, +year=2016 +} + +@article{BDLSY12, + author = {Daniel J. Bernstein and + Niels Duif and + Tanja Lange and + Peter Schwabe and + Bo{-}Yin Yang}, + title = {High-speed high-security signatures}, + journal = {J. Cryptographic Engineering}, + volume = {2}, + number = {2}, + pages = {77--89}, + year = {2012}, + url = {http://dx.doi.org/10.1007/s13389-012-0027-1}, + doi = {10.1007/s13389-012-0027-1}, + timestamp = {Tue, 30 Oct 2012 13:34:04 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/jce/BernsteinDLSY12}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Avialable at \url{https://ed25519.cr.yp.to/}} +} + + +@misc{Mil12, +author = {Andrew Miller}, +title = {Storing {UTXOs} in a Balanced {Merkle} Tree (zero-trust nodes with {O}(1)-storage)}, +note = {\url{https://bitcointalk.org/index.php?topic=101734.msg1117428}}, +year = 2012 +} + +@misc{utxo, +author = {Jameson Lopp}, +title = {Unspent Transactions Outputs in {B}itcoin}, +note = {\url{http://statoshi.info/dashboard/db/unspent-transaction-output-set}, accessed Nov 7, 2016} +} + +@misc{gzip, +key={gzip}, +author={{Jean-loup} Gailly and Mark Adler}, +title={{gzip}}, +note={\url{http://www.gzip.org/}} +} + +@misc{Mil16, +author = {Andrew Miller}, +title = {Private Communication}, +year = 2016 +} + +@inproceedings{Rog06, + author = {Phillip Rogaway}, + title = {Formalizing Human Ignorance}, + booktitle = {Progress in Cryptology - {VIETCRYPT} 2006, First International Conferenceon + Cryptology in Vietnam, Hanoi, Vietnam, September 25-28, 2006, Revised + Selected Papers}, + pages = {211--228}, + year = {2006}, + crossref = {DBLP:conf/vietcrypt/2006}, + url = {http://dx.doi.org/10.1007/11958239_14}, + doi = {10.1007/11958239_14}, + timestamp = {Fri, 06 Mar 2015 14:11:06 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/vietcrypt/Rogaway06}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{https://eprint.iacr.org/2006/281.pdf}} +} +@proceedings{DBLP:conf/vietcrypt/2006, + editor = {Phong Q. Nguyen}, + title = {Progressin Cryptology - {VIETCRYPT} 2006, First International Conferenceon + Cryptology in Vietnam, Hanoi, Vietnam, September 25-28, 2006, Revised + Selected Papers}, + series = {Lecture Notes in Computer Science}, + volume = {4341}, + publisher = {Springer}, + year = {2006}, + url = {http://dx.doi.org/10.1007/11958239}, + doi = {10.1007/11958239}, + isbn = {3-540-68799-8}, + timestamp = {Fri, 06 Mar 2015 14:11:06 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/vietcrypt/2006}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@book{Knu98v3, +author ={Knuth, Donald}, +title={The Art of Computer Programming: Volume 3: Sorting and Searching}, +edition={2nd}, +publisher={Addison-Wesley}, +year = 1998 +} + +@misc{ethereum-benchmark, +key={eth}, +title = {Transactions Per Block}, +note = {\url{https://github.com/ethereum/wiki/wiki/Benchmarks}} +} + + +@misc{tbp, +key={tbp}, +title = {Transactions Per Block}, +note = {\url{https://blockchain.info/charts/n-transactions-per-block}} +} + +@misc{spv, +key = {spv}, +title={{SPV}, Simplified Payment Verification}, +note = {\url{https://bitcoin.org/en/glossary/simplified-payment-verification}} +} + +@inproceedings{EK13, + author = {Mohammad Etemad and + Alptekin K{\"{u}}p{\c{c}}{\"{u}}}, + title = {Database Outsourcing with Hierarchical Authenticated Data Structures}, + booktitle = {Information Security and Cryptology - {ICISC} 2013 - 16th International + Conference, Seoul, Korea, November 27-29, 2013, Revised Selected Papers}, + pages = {381--399}, + year = {2013}, + crossref = {DBLP:conf/icisc/2013}, + url = {http://dx.doi.org/10.1007/978-3-319-12160-4_23}, + doi = {10.1007/978-3-319-12160-4_23}, + timestamp = {Wed, 22 Oct 2014 16:56:48 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/icisc/EtemadK13}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://eprint.iacr.org/2015/351}} +} +@proceedings{DBLP:conf/icisc/2013, + editor = {Hyang{-}Sook Lee and + Dong{-}Guk Han}, + title = {Information Security and Cryptology - {ICISC} 2013 - 16th International + Conference, Seoul, Korea, November 27-29, 2013, Revised Selected Papers}, + series = {Lecture Notes in Computer Science}, + volume = {8565}, + publisher = {Springer}, + year = {2014}, + url = {http://dx.doi.org/10.1007/978-3-319-12160-4}, + doi = {10.1007/978-3-319-12160-4}, + isbn = {978-3-319-12159-8}, + timestamp = {Wed, 22 Oct 2014 16:55:58 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/icisc/2013}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@inproceedings{BP07, + author = {Giuseppe Di Battista and + Bernardo Palazzi}, + title = {Authenticated Relational Tables and Authenticated Skip Lists}, + booktitle = {Data and Applications Security XXI, 21st Annual {IFIP} {WG} 11.3 Working + Conference on Data and Applications Security, Redondo Beach, CA, USA, + July 8-11, 2007, Proceedings}, + pages = {31--46}, + year = {2007}, + crossref = {DBLP:conf/dbsec/2007}, + url = {http://dx.doi.org/10.1007/978-3-540-73538-0_3}, + doi = {10.1007/978-3-540-73538-0_3}, + timestamp = {Tue, 30 Jun 2015 09:52:18 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/dbsec/BattistaP07}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://www.ece.umd.edu/~cpap/published/alex-ber-cpap-rt-08b.pdf}} +} +@proceedings{DBLP:conf/dbsec/2007, + editor = {Steve Barker and + Gail{-}Joon Ahn}, + title = {Data and Applications Security XXI, 21st Annual {IFIP} {WG} 11.3 Working + Conference on Data and Applications Security, Redondo Beach, CA, USA, + July 8-11, 2007, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {4602}, + publisher = {Springer}, + year = {2007}, + isbn = {978-3-540-73533-5}, + timestamp = {Tue, 30 Jun 2015 09:52:18 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/dbsec/2007}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{HPPT08, + author = {Alexander Heitzmann and + Bernardo Palazzi and + Charalampos Papamanthou and + Roberto Tamassia}, + title = {Efficient integrity checking of untrusted network storage}, + booktitle = {Proceedings of the 2008 {ACM} Workshop On Storage Security And Survivability, + StorageSS 2008, Alexandria, VA, USA, October 31, 2008}, + pages = {43--54}, + year = {2008}, + crossref = {DBLP:conf/storagess/2008}, + url = {http://doi.acm.org/10.1145/1456469.1456479}, + doi = {10.1145/1456469.1456479}, + timestamp = {Tue, 11 Nov 2008 13:01:27 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/storagess/HeitzmannPPT08}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://www.ece.umd.edu/~cpap/published/alex-ber-cpap-rt-08b.pdf}} +} +@proceedings{DBLP:conf/storagess/2008, + editor = {Yongdae Kim and + William Yurcik}, + title = {Proceedings of the 2008 {ACM} Workshop On Storage Security And Survivability, + StorageSS 2008, Alexandria, VA, USA, October 31, 2008}, + publisher = {{ACM}}, + year = {2008}, + isbn = {978-1-60558-299-3}, + timestamp = {Tue, 11 Nov 2008 12:59:27 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/storagess/2008}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{GPTT08, + author = {Michael T. Goodrich and + Charalampos Papamanthou and + Roberto Tamassia and + Nikos Triandopoulos}, + title = {Athos: Efficient Authentication of Outsourced File Systems}, + booktitle = {Information Security, 11th International Conference, {ISC} 2008, Taipei, + Taiwan, September 15-18, 2008. Proceedings}, + pages = {80--96}, + year = {2008}, + crossref = {DBLP:conf/isw/2008}, + url = {http://dx.doi.org/10.1007/978-3-540-85886-7_6}, + doi = {10.1007/978-3-540-85886-7_6}, + timestamp = {Mon, 22 Sep 2008 15:16:10 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/isw/GoodrichPTT08}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://www.ece.umd.edu/~cpap/published/mtg-cpap-rt-nikos-08.pdf}} +} +@proceedings{DBLP:conf/isw/2008, + editor = {Tzong{-}Chen Wu and + Chin{-}Laung Lei and + Vincent Rijmen and + Der{-}Tsai Lee}, + title = {Information Security, 11th International Conference, {ISC} 2008, Taipei, + Taiwan, September 15-18, 2008. Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {5222}, + publisher = {Springer}, + year = {2008}, + isbn = {978-3-540-85884-3}, + timestamp = {Mon, 22 Sep 2008 15:14:53 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/isw/2008}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +} +@article{Pug90, + author = {William Pugh}, + title = {Skip Lists: {A} Probabilistic Alternative to Balanced Trees}, + journal = {Commun. {ACM}}, + volume = {33}, + number = {6}, + pages = {668--676}, + year = {1990}, + url = {http://doi.acm.org/10.1145/78973.78977}, + doi = {10.1145/78973.78977}, + timestamp = {Tue, 07 Jun 2011 16:52:04 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/cacm/Pugh90}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available from \url{http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.15.9072}} +} + +@phdthesis{Pap11, +author = {Charalampos Papamanthou}, +title = {Cryptography for Efficiency: New Directions in Authenticated Data Structures}, +school = {Brown University}, +year = 2011, +note = {Available at \url{http://www.ece.umd.edu/~cpap/published/theses/cpap-phd.pdf}} +} + +@inproceedings{GSTW03, + author = {Michael T. Goodrich and + Michael Shin and + Roberto Tamassia and + William H. Winsborough}, + title = {Authenticated Dictionaries for Fresh Attribute Credentials}, + booktitle = {Trust Management, First International Conference, iTrust 2003, Heraklion, + Crete, Greece, May 28-30, 2002, Proceedings}, + pages = {332--347}, + year = {2003}, + crossref = {DBLP:conf/itrust/2003}, + url = {http://dx.doi.org/10.1007/3-540-44875-6_24}, + doi = {10.1007/3-540-44875-6_24}, + timestamp = {Tue, 05 Jul 2011 11:07:29 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/itrust/GoodrichSTW03}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://cs.brown.edu/cgc/stms/papers/itrust2003.pdf}} +} +@proceedings{DBLP:conf/itrust/2003, + editor = {Paddy Nixon and + Sotirios Terzis}, + title = {Trust Management, First International Conference, iTrust 2003, Heraklion, + Crete, Greece, May 28-30, 2002, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {2692}, + publisher = {Springer}, + year = {2003}, + isbn = {3-540-40224-1}, + timestamp = {Fri, 30 May 2003 15:06:43 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/itrust/2003}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@article{CLHML15, + author = {Xiaofeng Chen and + Jin Li and + Xinyi Huang and + Jianfeng Ma and + Wenjing Lou}, + title = {New Publicly Verifiable Databases with Efficient Updates}, + journal = {{IEEE} Trans. Dependable Sec. Comput.}, + volume = {12}, + number = {5}, + pages = {546--556}, + year = {2015}, + url = {http://dx.doi.org/10.1109/TDSC.2014.2366471}, + doi = {10.1109/TDSC.2014.2366471}, + timestamp = {Thu, 21 Jul 2016 12:12:02 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/tdsc/0001LHML15}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@article{CLWML16, + author = {Xiaofeng Chen and + Jin Li and + Jian Weng and + Jianfeng Ma and + Wenjing Lou}, + title = {Verifiable Computation over Large Database with Incremental Updates}, + journal = {{IEEE} Trans. Computers}, + volume = {65}, + number = {10}, + pages = {3184--3195}, + year = {2016}, + url = {http://dx.doi.org/10.1109/TC.2015.2512870}, + doi = {10.1109/TC.2015.2512870}, + timestamp = {Mon, 19 Sep 2016 15:58:56 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/tc/00010WML16}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@article{MWMS16, +title = {Publicly verifiable databases with efficient insertion/deletion operations}, +author = {Meixia Miao and Jianfeng Wang and Jianfeng Ma and Willy Susilo}, +year = 2016, +journal = {Journal of Computer and System Sciences}, +note = {Available on-line at \url{http://dx.doi.org/10.1016/j.jcss.2016.07.005}. To appearn in print.} +} + +@inproceedings{BGV11, + author = {Siavosh Benabbas and + Rosario Gennaro and + Yevgeniy Vahlis}, + title = {Verifiable Delegation of Computation over Large Datasets}, + booktitle = {Advances in Cryptology - {CRYPTO} 2011 - 31st Annual Cryptology Conference, + Santa Barbara, CA, USA, August 14-18, 2011. Proceedings}, + pages = {111--131}, + year = {2011}, + crossref = {DBLP:conf/crypto/2011}, + url = {http://dx.doi.org/10.1007/978-3-642-22792-9_7}, + doi = {10.1007/978-3-642-22792-9_7}, + timestamp = {Mon, 15 Aug 2011 21:29:40 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/crypto/BenabbasGV11}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://eprint.iacr.org/2011/132}} +} +@proceedings{DBLP:conf/crypto/2011, + editor = {Phillip Rogaway}, + title = {Advances in Cryptology - {CRYPTO} 2011 - 31st Annual Cryptology Conference, + Santa Barbara, CA, USA, August 14-18, 2011. Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {6841}, + publisher = {Springer}, + year = {2011}, + url = {http://dx.doi.org/10.1007/978-3-642-22792-9}, + doi = {10.1007/978-3-642-22792-9}, + isbn = {978-3-642-22791-2}, + timestamp = {Mon, 15 Aug 2011 21:26:36 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/crypto/2011}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{CF13, + author = {Dario Catalano and + Dario Fiore}, + title = {Vector Commitments and Their Applications}, + booktitle = {Public-Key Cryptography - {PKC} 2013 - 16th International Conference + on Practice and Theory in Public-Key Cryptography, Nara, Japan, February + 26 - March 1, 2013. Proceedings}, + pages = {55--72}, + year = {2013}, + crossref = {DBLP:conf/pkc/2013}, + url = {http://dx.doi.org/10.1007/978-3-642-36362-7_5}, + doi = {10.1007/978-3-642-36362-7_5}, + timestamp = {Tue, 05 Feb 2013 13:29:13 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/pkc/CatalanoF13}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://eprint.iacr.org/2011/495}} +} +@proceedings{DBLP:conf/pkc/2013, + editor = {Kaoru Kurosawa and + Goichiro Hanaoka}, + title = {Public-Key Cryptography - {PKC} 2013 - 16th International Conference + on Practice and Theory in Public-Key Cryptography, Nara, Japan, February + 26 - March 1, 2013. Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {7778}, + publisher = {Springer}, + year = {2013}, + url = {http://dx.doi.org/10.1007/978-3-642-36362-7}, + doi = {10.1007/978-3-642-36362-7}, + isbn = {978-3-642-36361-0}, + timestamp = {Tue, 05 Feb 2013 13:28:08 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/pkc/2013}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@unpublished{Whi15, +author = {Bill White}, +title = {A Theory for Lightweight Cryptocurrency +Ledgers}, +year = 2015, +note = {Available at \url{http://qeditas.org/lightcrypto.pdf} (see also code at \url{https://github.com/bitemyapp/ledgertheory})} +} + +@article{PTT16, + author = {Charalampos Papamanthou and + Roberto Tamassia and + Nikos Triandopoulos}, + title = {Authenticated Hash Tables Based on Cryptographic Accumulators}, + journal = {Algorithmica}, + volume = {74}, + number = {2}, + pages = {664--712}, + year = {2016}, + url = {http://dx.doi.org/10.1007/s00453-014-9968-3}, + doi = {10.1007/s00453-014-9968-3}, + timestamp = {Wed, 27 Jan 2016 13:00:22 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/algorithmica/PapamanthouTT16}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@book{Wei06, +author = {Mark Allen Weiss}, +title = {Data Structures and Algorithm Analysis in Java (Second Edition)}, +publisher = {Pearson}, +year = 2006 +} + +@misc{Pfa02, +author = {Ben Pfaff}, +title = {{GNU} libavl 2.0.2}, +note = {Available at \url{http://adtinfo.org/libavl.html/index.html}}, +year = 2002 +} + +@misc{impl, +key = {code}, +title = {Implementation of Authenticated Data Structures within Scorex}, +note = {\url{https://github.com/input-output-hk/scrypto/}} +} + +@misc{bscript, +key = {BSC}, +title = {Script - Bitcoin Wiki}, +note = {\url{https://en.bitcoin.it/wiki/Script}} +} + +@misc{Java, +key = {JAV}, +title = {The {Java} Programming Language}, +note = {\url{http://www.java.org/}} +} + +@misc{scala, +key = {SCL}, +title = {The {Scala} Programming Language}, +note = {\url{http://www.scala-lang.org/}} +} + +@misc{scapi, +key = {SCP}, +title = {Secure Computation API}, +note = {\url{https://cyber.biu.ac.il/scapi/}} +} + +@misc{bouncycastle, +key = {LBC}, +title = {The Legion of the Bouncy Castle}, +note = {\url{https://www.bouncycastle.org}} +} + +@misc{nxt, +key = {nxt}, +title = {The {Nxt} Cryptocurrency}, +note = {\url{https://nxt.org/}} +} + +@article{AVL62, + author = {Adel'son-Vel'skii and Landis}, + title = {An algorithm for the organization of information}, +journal = {Dokladi Akademia Nauk SSSR}, + volume = 146, +number = 2, +page = {263--266}, +year=1962, +note ={English translation in Soviet Math. Doklady 3, 1962, 1259--1263} +} +@misc{Sed08, + author = {Robert Sedgewick}, + title = {Left-leaning Red-Black Trees}, + year = 2008, +note = {Available at \url{http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf}} +} +@inproceedings{GB78, + author = {Leonidas J. Guibas and + Robert Sedgewick}, + title = {A Dichromatic Framework for Balanced Trees}, + booktitle = {19th Annual Symposium on Foundations of Computer Science, Ann Arbor, + Michigan, USA, 16-18 October 1978}, + pages = {8--21}, + year = {1978}, + crossref = {DBLP:conf/focs/FOCS19}, + url = {http://dx.doi.org/10.1109/SFCS.1978.3}, + doi = {10.1109/SFCS.1978.3}, + timestamp = {Tue, 16 Dec 2014 09:57:19 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/focs/GuibasS78}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available from \url{http://professor.ufabc.edu.br/~jesus.mena/courses/mc3305-2q-2015/AED2-13-redblack-paper.pdf}} +} +@proceedings{DBLP:conf/focs/FOCS19, + title = {19th Annual Symposium on Foundations of Computer Science, Ann Arbor, + Michigan, USA, 16-18 October 1978}, + publisher = {{IEEE} Computer Society}, + year = {1978}, + url = {http://ieeexplore.ieee.org/xpl/mostRecentIssue.jsp?punumber=4567951}, + timestamp = {Mon, 15 Dec 2014 18:48:44 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/focs/FOCS19}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@article{MR98, + author = {Conrado Mart{\'{\i}}nez and + Salvador Roura}, + title = {Randomized Binary Search Trees}, + journal = {J. {ACM}}, + volume = {45}, + number = {2}, + pages = {288--323}, + year = {1998}, + url = {http://doi.acm.org/10.1145/274787.274812}, + doi = {10.1145/274787.274812}, + timestamp = {Wed, 19 Apr 2006 10:08:18 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/jacm/MartinezR98}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Availabel at \url{http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.243}} +} +@article{SA96, + author = {Raimund Seidel and + Cecilia R. Aragon}, + title = {Randomized Search Trees}, + journal = {Algorithmica}, + volume = {16}, + number = {4/5}, + pages = {464--497}, + year = {1996}, + url = {http://dx.doi.org/10.1007/BF01940876}, + doi = {10.1007/BF01940876}, + timestamp = {Wed, 18 May 2011 16:16:57 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/algorithmica/SeidelA96}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{https://faculty.washington.edu/aragon/pubs/rst96.pdf}} +} + +@article{CW11, + author = {Scott A. Crosby and + Dan S. Wallach}, + title = {Authenticated Dictionaries: Real-World Costs and Trade-Offs}, + journal = {{ACM} Trans. Inf. Syst. Secur.}, + volume = {14}, + number = {2}, + pages = {17}, + year = {2011}, + url = {http://doi.acm.org/10.1145/2019599.2019602}, + doi = {10.1145/2019599.2019602}, + timestamp = {Thu, 03 Nov 2011 15:33:19 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/tissec/CrosbyW11}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://tamperevident.cs.rice.edu/Storage.html}} +} +@inproceedings{AGT01, + author = {Aris Anagnostopoulos and + Michael T. Goodrich and + Roberto Tamassia}, + title = {Persistent Authenticated Dictionaries and Their Applications}, + booktitle = {Information Security, 4th International Conference, {ISC} 2001, Malaga, + Spain, October 1-3, 2001, Proceedings}, + pages = {379--393}, + year = {2001}, + crossref = {DBLP:conf/isw/2001}, + url = {http://dx.doi.org/10.1007/3-540-45439-X_26}, + doi = {10.1007/3-540-45439-X_26}, + timestamp = {Tue, 28 Jun 2011 15:13:09 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/isw/AnagnostopoulosGT01}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://aris.me/pubs/pad.pdf}} +} +@proceedings{DBLP:conf/isw/2001, + editor = {George I. Davida and + Yair Frankel}, + title = {Information Security, 4th International Conference, {ISC} 2001, Malaga, + Spain, October 1-3, 2001, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {2200}, + publisher = {Springer}, + year = {2001}, + isbn = {3-540-42662-0}, + timestamp = {Wed, 17 Apr 2002 08:43:13 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/isw/2001}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@article{NN00, + author = {Moni Naor and + Kobbi Nissim}, + title = {Certificate revocation and certificate update}, + journal = {{IEEE} Journal on Selected Areas in Communications}, + volume = {18}, + number = {4}, + pages = {561--570}, + year = {2000}, + url = {http://dx.doi.org/10.1109/49.839932}, + doi = {10.1109/49.839932}, + timestamp = {Fri, 17 Feb 2012 09:46:39 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/jsac/NaorN00}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.7072}} +} + +@inproceedings{BEGKN91, + author = {Manuel Blum and + William S. Evans and + Peter Gemmell and + Sampath Kannan and + Moni Naor}, + title = {Checking the Correctness of Memories}, + booktitle = {32nd Annual Symposium on Foundations of Computer Science, San Juan, + Puerto Rico, 1-4 October 1991}, + pages = {90--99}, + year = {1991}, + crossref = {DBLP:conf/focs/FOCS32}, + url = {http://dx.doi.org/10.1109/SFCS.1991.185352}, + doi = {10.1109/SFCS.1991.185352}, + timestamp = {Tue, 16 Dec 2014 09:57:25 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/focs/BlumEGKN91}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Later appears as \cite{BEGKN94}, which is available at \url{http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.29.2991}} +} + +@article{BEGKN94, + author = {Manuel Blum and + William S. Evans and + Peter Gemmell and + Sampath Kannan and + Moni Naor}, + title = {Checking the Correctness of Memories}, + journal = {Algorithmica}, + volume = {12}, + number = {2/3}, + pages = {225--244}, + year = {1994}, + url = {http://dx.doi.org/10.1007/BF01185212}, + doi = {10.1007/BF01185212}, + timestamp = {Wed, 18 May 2011 16:16:50 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/algorithmica/BlumEGKN94}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.29.2991}}} +} + +@proceedings{DBLP:conf/focs/FOCS32, + title = {32nd Annual Symposium on Foundations of Computer Science, San Juan, + Puerto Rico, 1-4 October 1991}, + publisher = {{IEEE} Computer Society}, + year = {1991}, + url = {http://ieeexplore.ieee.org/xpl/mostRecentIssue.jsp?punumber=379}, + isbn = {0-8186-2445-0}, + timestamp = {Mon, 15 Dec 2014 18:48:44 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/focs/FOCS32}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@unpublished{Nak08, +author = {Satoshi Nakamoto}, +title = {Bitcoin: A Peer-to-Peer Electronic Cash System}, +year = 2008, +note = {\url{https://bitcoin.org/bitcoin.pdf}} +} + +@inproceedings{PT07, + author = {Charalampos Papamanthou and + Roberto Tamassia}, + title = {Time and Space Efficient Algorithms for Two-Party Authenticated Data + Structures}, + booktitle = {Information and Communications Security, 9th International Conference, + {ICICS} 2007, Zhengzhou, China, December 12-15, 2007, Proceedings}, + pages = {1--15}, + year = {2007}, + crossref = {DBLP:conf/icics/2007}, + url = {http://dx.doi.org/10.1007/978-3-540-77048-0_1}, + doi = {10.1007/978-3-540-77048-0_1}, + timestamp = {Tue, 21 Oct 2008 14:44:58 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/icics/PapamanthouT07}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://www.ece.umd.edu/~cpap/published/cpap-rt-07.pdf}} +} +@proceedings{DBLP:conf/icics/2007, + editor = {Sihan Qing and + Hideki Imai and + Guilin Wang}, + title = {Information and Communications Security, 9th International Conference, + {ICICS} 2007, Zhengzhou, China, December 12-15, 2007, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {4861}, + publisher = {Springer}, + year = {2008}, + isbn = {978-3-540-77047-3}, + timestamp = {Tue, 21 Oct 2008 14:44:58 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/icics/2007}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{DJ07, + author = {Brian C. Dean and + Zachary H. Jones}, + title = {Exploring the duality between skip lists and binary search trees}, + booktitle = {Proceedings of the 45th Annual Southeast Regional Conference, 2007, + Winston-Salem, North Carolina, USA, March 23-24, 2007}, + pages = {395--399}, + year = {2007}, + crossref = {DBLP:conf/ACMse/2007}, + url = {http://doi.acm.org/10.1145/1233341.1233413}, + doi = {10.1145/1233341.1233413}, + timestamp = {Wed, 02 May 2007 15:10:53 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/ACMse/DeanJ07}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{https://people.cs.clemson.edu/~bcdean/skip_bst.pdf}} +} +@proceedings{DBLP:conf/ACMse/2007, + editor = {David John and + Sandria N. Kerr}, + title = {Proceedings of the 45th Annual Southeast Regional Conference, 2007, + Winston-Salem, North Carolina, USA, March 23-24, 2007}, + publisher = {{ACM}}, + year = {2007}, + isbn = {978-1-59593-629-5}, + timestamp = {Wed, 02 May 2007 15:10:53 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/ACMse/2007}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{GPT07, + author = {Michael T. Goodrich and + Charalampos Papamanthou and + Roberto Tamassia}, + title = {On the Cost of Persistence and Authentication in Skip Lists}, + booktitle = {Experimental Algorithms, 6th International Workshop, {WEA} 2007, Rome, + Italy, June 6-8, 2007, Proceedings}, + pages = {94--107}, + year = {2007}, + crossref = {DBLP:conf/wea/2007}, + url = {http://dx.doi.org/10.1007/978-3-540-72845-0_8}, + doi = {10.1007/978-3-540-72845-0_8}, + timestamp = {Thu, 28 Jun 2007 15:26:47 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/wea/GoodrichPT07}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://cs.brown.edu/cgc/stms/papers/pers-auth.pdf}} +} +@proceedings{DBLP:conf/wea/2007, + editor = {Camil Demetrescu}, + title = {Experimental Algorithms, 6th International Workshop, {WEA} 2007, Rome, + Italy, June 6-8, 2007, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {4525}, + publisher = {Springer}, + year = {2007}, + isbn = {978-3-540-72844-3}, + timestamp = {Thu, 28 Jun 2007 15:26:47 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/wea/2007}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +@inproceedings{MHKS14, + author = {Andrew Miller and + Michael Hicks and + Jonathan Katz and + Elaine Shi}, + title = {Authenticated data structures, generically}, + booktitle = {The 41st Annual {ACM} {SIGPLAN-SIGACT} Symposium on Principles of + Programming Languages, {POPL} '14, San Diego, CA, USA, January 20-21, + 2014}, + pages = {411--424}, + year = {2014}, + crossref = {DBLP:conf/popl/2014}, + url = {http://doi.acm.org/10.1145/2535838.2535851}, + doi = {10.1145/2535838.2535851}, + timestamp = {Thu, 09 Jan 2014 08:32:32 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/popl/MillerHKS14}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Project page and full version at \url{http://amiller.github.io/lambda-auth/paper.html}} +} +@proceedings{DBLP:conf/popl/2014, + editor = {Suresh Jagannathan and + Peter Sewell}, + title = {The 41st Annual {ACM} {SIGPLAN-SIGACT} Symposium on Principles of + Programming Languages, {POPL} '14, San Diego, CA, USA, January 20-21, + 2014}, + publisher = {{ACM}}, + year = {2014}, + url = {http://dl.acm.org/citation.cfm?id=2535838}, + isbn = {978-1-4503-2544-8}, + timestamp = {Thu, 09 Jan 2014 08:21:22 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/popl/2014}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} +homebrowsesearchabout + +@article{ORS04, + author = {Rafail Ostrovsky and + Charles Rackoff and + Adam D. Smith}, + title = {Efficient Consistency Proofs for Generalized Queries on a Committed + Database}, + journal = {{IACR} Cryptology ePrint Archive}, + volume = {2004}, + pages = {170}, + year = {2004}, + url = {http://eprint.iacr.org/2004/170}, + timestamp = {Fri, 08 Apr 2016 07:34:31 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/iacr/OstrovskyRS04}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://eprint.iacr.org/2004/170}} +} + +@article{MNDGKS04, + author = {Charles U. Martel and + Glen Nuckolls and + Premkumar T. Devanbu and + Michael Gertz and + April Kwong and + Stuart G. Stubblebine}, + title = {A General Model for Authenticated Data Structures}, + journal = {Algorithmica}, + volume = {39}, + number = {1}, + pages = {21--41}, + year = {2004}, + url = {http://dx.doi.org/10.1007/s00453-003-1076-8}, + doi = {10.1007/s00453-003-1076-8}, + timestamp = {Fri, 16 Jul 2004 14:34:49 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/journals/algorithmica/MartelNDGKS04}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.75.3658}} +} + +@unpublished{GTS01, +author={M.T. Goodrich and R. Tamassia and A. Schwerin}, +title={Implementation of an authenticated dictionary with skip lists and commutative hashing}, +note={Available at \url{http://cs.brown.edu/cgc/stms/papers/discex2001.pdf}; also presented in Proc. {DARPA} Information +Survivability Conference \& Exposition {II} {(DISCEX II)}}, +year = 2001 +} + + +@unpublished{GT00, + author = {M. T. Goodrich and R. Tamassia}, +title={Efficient authenticated dictionaries +with skip lists and commutative hashing}, +note = {Technical +Report, Johns Hopkins Information Security Institute; available at \url{http://cs.brown.edu/cgc/stms/papers/hashskip.pdf}}, +year= 2000 +} + +@inproceedings{Mer89, + author = {Ralph C. Merkle}, + title = {A Certified Digital Signature}, + booktitle = {Advances in Cryptology - {CRYPTO} '89, 9th Annual International Cryptology + Conference, Santa Barbara, California, USA, August 20-24, 1989, Proceedings}, + pages = {218--238}, + year = {1989}, + crossref = {DBLP:conf/crypto/1989}, + url = {http://dx.doi.org/10.1007/0-387-34805-0_21}, + doi = {10.1007/0-387-34805-0_21}, + timestamp = {Fri, 18 Sep 2009 09:03:51 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/crypto/Merkle89}, + bibsource = {dblp computer science bibliography, http://dblp.org}, + note = {Available at \url{http://www.merkle.com/papers/Certified1979.pdf}} +} +@proceedings{DBLP:conf/crypto/1989, + editor = {Gilles Brassard}, + title = {Advances in Cryptology - {CRYPTO} '89, 9th Annual International Cryptology + Conference, Santa Barbara, California, USA, August 20-24, 1989, Proceedings}, + series = {Lecture Notes in Computer Science}, + volume = {435}, + publisher = {Springer}, + year = {1990}, + isbn = {3-540-97317-6}, + timestamp = {Thu, 07 Feb 2002 09:41:00 +0100}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/crypto/1989}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + +@inproceedings{BGM16, + author = {Iddo Bentov and + Ariel Gabizon and + Alex Mizrahi}, + title = {Cryptocurrencies Without Proof of Work}, + booktitle = {Financial Cryptography and Data Security - {FC} 2016 International + Workshops, BITCOIN, VOTING, and WAHC, Christ Church, Barbados, February + 26, 2016, Revised Selected Papers}, + pages = {142--157}, + year = {2016}, + crossref = {DBLP:conf/fc/2016w}, + url = {http://dx.doi.org/10.1007/978-3-662-53357-4_10}, + doi = {10.1007/978-3-662-53357-4_10}, + timestamp = {Wed, 31 Aug 2016 11:20:19 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fc/BentovGM16}, + bibsource = {dblp computer science bibliography, http://dblp.org}, +note = {Available at \url{http://arxiv.org/abs/1406.5694}} +} + +@proceedings{DBLP:conf/fc/2016w, + editor = {Jeremy Clark and + Sarah Meiklejohn and + Peter Y. A. Ryan and + Dan S. Wallach and + Michael Brenner and + Kurt Rohloff}, + title = {Financial Cryptography and Data Security - {FC} 2016 International + Workshops, BITCOIN, VOTING, and WAHC, Christ Church, Barbados, February + 26, 2016, Revised Selected Papers}, + series = {Lecture Notes in Computer Science}, + volume = {9604}, + publisher = {Springer}, + year = {2016}, + url = {http://dx.doi.org/10.1007/978-3-662-53357-4}, + doi = {10.1007/978-3-662-53357-4}, + isbn = {978-3-662-53356-7}, + timestamp = {Wed, 31 Aug 2016 11:18:50 +0200}, + biburl = {http://dblp.uni-trier.de/rec/bib/conf/fc/2016w}, + bibsource = {dblp computer science bibliography, http://dblp.org} +} + + +@misc{kiayias2016, + author = {Aggelos Kiayias and Ioannis Konstantinou and Alexander Russell and Bernardo David and Roman Oliynykov}, + title = {A Provably Secure Proof-of-Stake Blockchain Protocol}, + howpublished = {Cryptology ePrint Archive, Report 2016/889}, + year = {2016}, + note = {\url{http://eprint.iacr.org/2016/889}}, +} + +@inproceedings{croman2016scaling, + title={On scaling decentralized blockchains}, + author={Croman, Kyle and Decker, Christian and Eyal, Ittay and Gencer, Adem Efe and Juels, Ari and Kosba, Ahmed and Miller, Andrew and Saxena, Prateek and Shi, Elaine and G{\"u}n, Emin}, + booktitle={Proc. 3rd Workshop on Bitcoin and Blockchain Research}, + year={2016} +} + +@inproceedings{decker2013information, + title={Information propagation in the bitcoin network}, + author={Decker, Christian and Wattenhofer, Roger}, + booktitle={IEEE P2P 2013 Proceedings}, + pages={1--10}, + year={2013}, + organization={IEEE} +} + +@inproceedings{sompolinsky2015secure, + title={Secure high-rate transaction processing in Bitcoin}, + author={Sompolinsky, Yonatan and Zohar, Aviv}, + booktitle={International Conference on Financial Cryptography and Data Security}, + pages={507--527}, + year={2015}, + organization={Springer} +} + +@inproceedings{eyal2016bitcoin, + title={Bitcoin-NG: A scalable blockchain protocol}, + author={Eyal, Ittay and Gencer, Adem Efe and Sirer, Emin G{\"u}n and Van Renesse, Robbert}, + booktitle={13th USENIX Symposium on Networked Systems Design and Implementation (NSDI 16)}, + pages={45--59}, + year={2016} +} + +@article{kokoris2016enhancing, + title={Enhancing bitcoin security and performance with strong consistency via collective signing}, + author={Kokoris-Kogias, Eleftherios and Jovanovic, Philipp and Gailly, Nicolas and Khoffi, Ismail and Gasser, Linus and Ford, Bryan}, + journal={arXiv preprint arXiv:1602.06997}, + year={2016} +} + +@inproceedings{aumasson2013blake2, + title={{BLAKE2}: simpler, smaller, fast as {MD5}}, + author={Aumasson, Jean-Philippe and Neves, Samuel and Wilcox-O’Hearn, Zooko and Winnerlein, Christian}, + booktitle={International Conference on Applied Cryptography and Network Security}, + pages={119--135}, + year={2013}, + organization={Springer} +} + +@misc{fullnodes, + title={The Decline in Bitcoin Full Nodes}, + author={Parker, Luke}, + year=2015, + note={\url{http://bravenewcoin.com/news/the-decline-in-bitcoins-full-nodes/}} +} + +@misc{bitcoindos, + title={{CVE}-2013-2293: New {DoS} vulnerability by Forcing Continuous Hard Disk Seek/Read Activity}, + author={Bitcoin Wiki}, + year=2013, + note={\url{https://en.bitcoin.it/wiki/CVE-2013-2293}} +} + +@misc{ethdos, + title={Transaction spam attack: Next Steps}, + author={Vitalik Buterin}, + year=2016, + note={\url{https://blog.ethereum.org/2016/09/22/transaction-spam-attack-next-steps/}} +} + +@misc{geth, + title={Official golang implementation of the Ethereum protocol}, + author={The Go Ethereum Team}, + year=2016, + note={\url{http://geth.ethereum.org/}} +} + +@misc{fivehrs, + title={A Bitcoin transaction that takes 5 hours to verify}, + author={Sergio Lerner}, + year=2017, + note={\url{https://bitslog.wordpress.com/2017/01/08/a-bitcoin-transaction-that-takes-5-hours-to-verify/}} +} + +@misc{ethattacks, + title={Ethereum Network Attacker’s IP Address Is Traceable}, + author={Bok Khoo}, + year=2016, + note={\url{https://www.bokconsulting.com.au/blog/ethereum-network-attackers-ip-address-is-traceable/}} +} + +@article{courtois2014subversive, + title={On subversive miner strategies and block withholding attack in bitcoin digital currency}, + author={Courtois, Nicolas T and Bahack, Lear}, + journal={arXiv preprint arXiv:1402.1718}, + year={2014} +} + +@inproceedings{luu2017smartpool, + title={Smartpool: Practical decentralized pooled mining}, + author={Luu, Loi and Velner, Yaron and Teutsch, Jason and Saxena, Prateek}, + booktitle={26th $\{$USENIX$\}$ Security Symposium ($\{$USENIX$\}$ Security 17)}, + pages={1409--1426}, + year={2017} +} + +@phdthesis{chesterman2018p2pool, + title={THE P2POOL MINING POOL}, + author={Chesterman, Xavier}, + year={2018}, + school={Ghent University} +} \ No newline at end of file diff --git a/docs/zerojoin/main.pdf b/docs/zerojoin/main.pdf new file mode 100644 index 0000000000..98a8886a00 Binary files /dev/null and b/docs/zerojoin/main.pdf differ diff --git a/docs/zerojoin/main.tex b/docs/zerojoin/main.tex new file mode 100644 index 0000000000..d638c05614 --- /dev/null +++ b/docs/zerojoin/main.tex @@ -0,0 +1,651 @@ +\documentclass[runningheads]{llncs} +\def\shownotes{1} +\def\notesinmargins{0} + +%\usepackage{fullpage} +\usepackage{fancyvrb} +\usepackage{mathtools,color,xcolor,hyperref,graphicx,wrapfig,listings,array,xspace} +\usepackage{caption} +\usepackage{subcaption} +\usepackage{tikz} +\usetikzlibrary{arrows} + +\usepackage{amsfonts} +% https://tex.stackexchange.com/questions/11719/how-do-i-backcolor-in-verbatim +\usepackage{xcolor} +\usepackage{alltt} +% Compensate for fbox sep: +\newcommand\Hi[2][lightgray]{% + \hspace*{-\fboxsep}% + \colorbox{#1}{#2}% + \hspace*{-\fboxsep}% +} + +\ifnum\shownotes=1 +\ifnum\notesinmargins=1 +\newcommand{\authnote}[2]{\marginpar{\parbox{\marginparwidth}{\small % + \textsf{#1 {\textcolor{blue}{notes: #2}}}}}% + \textcolor{blue}{\textbf{\dag}}} +\else +\newcommand{\authnote}[2]{ + \textsf{#1 \textcolor{blue}{: #2}}} +\fi +\else +\newcommand{\authnote}[2]{} +\fi + +\newcommand{\snote}[1]{{\authnote{\textcolor{yellow}{Scalahub notes}}{#1}}} +\newcommand{\knote}[1]{{\authnote{\textcolor{green}{kushti notes}}{#1}}} + + +\newcommand{\ret}{\mathsf{ret}} +\newcommand{\new}{\mathsf{new}} +\newcommand{\hnew}{h_\mathsf{new}} +\newcommand{\old}{\mathsf{old}} +\newcommand{\op}{\mathsf{op}} +\newcommand{\verifier}{\mathcal{V}} +\newcommand{\prover}{\mathcal{P}} +\newcommand{\key}{\mathsf{key}} +\newcommand{\nextkey}{\mathsf{nextKey}} +\newcommand{\node}{\mathsf{t}} +\newcommand{\parent}{\mathsf{p}} +\newcommand{\leaf}{\mathsf{f}} +\newcommand{\vl}{\mathsf{value}} +\newcommand{\balance}{\mathsf{balance}} +\newcommand{\lft}{\mathsf{left}} +\newcommand{\rgt}{\mathsf{right}} +\newcommand{\lbl}{\mathsf{label}} +\newcommand{\direction}{\mathsf{d}} +\newcommand{\oppositedirection}{\bar{\mathsf{d}}} +\newcommand{\found}{\mathsf{found}} +\newcommand{\mypar}[1]{\smallskip\noindent\textbf{#1.}\ \ \ } +\newcommand{\ignore}[1]{} + +\newcommand{\langname}{ErgoScript\xspace} +\newcommand{\powname}{Autolykos\xspace} +\newcommand{\poolname}{ErgoPool\xspace} +\newcommand{\mixname}{ErgoMix\xspace} + + +\newcommand{\lst}[1]{\text{\lstinline[basicstyle={\ttfamily}]$#1$}} + +\newcommand{\andnode}{\ensuremath{\mathsf{AND}}} +\newcommand{\ornode}{\ensuremath{\mathsf{OR}}} +\newcommand{\tnode}{\ensuremath{\mathsf{THRESHOLD}}} + +\newcommand{\primname}{Commitment of Work\xspace} +\newcommand{\prim}{CoW\xspace} + + +\newcommand{\GF}{\ensuremath{\mathrm{GF}}} + + +\begin{document} + +\newcommand{\algname}{ZeroJoin\xspace} +\title{\algname: Combining ZeroCoin and CoinJoin} + +\author{Alexander Chepurnoy\inst{1,2}, Amitabh Saxena\inst{1}} + +\institute{Ergo Platform \\\email{\{kushti\}@protonmail.ch, \{amitabh123\}@gmail.com} \and +IOHK Research \\\email{\{alex.chepurnoy\}@iohk.io}} + +\maketitle + +\begin{abstract} + +We present \algname, a practical privacy-enhancing protocol for blockchain transactions. \algname can be considered a combination of ZeroCoin and CoinJoin. It borrows ideas from both but attempts to overcome some of their drawbacks. +Like ZeroCoin, our protocol uses zero-knowledge proofs and a pool of +participants. However, unlike ZeroCoin, our proofs are very efficient, and our pool size is not monotonically increasing. +Thus, our protocol overcomes the two major drawbacks of ZeroCoin. Our approach can also be considered a non-interactive variant of CoinJoin, where the interaction is replaced by a public transaction on the blockchain. The security of \algname is based on the Decision Diffie-Hellman (DDH) assumption. +We also present \mixname, a practical implementation of \algname on top of Ergo, a smart contract platform based on Sigma protocols. While \algname contains the key ideas, it leaves open the practical issue of handling fees. The key contribution of \mixname is a novel approach to handle fee in \algname. + +\end{abstract} + +\section{Introduction} +\label{intro} + +Privacy enhancing techniques in blockchains generally fall into two categories. The first is hiding the amounts being transferred, such as in Confidential Transactions~\cite{ct}. The second is obscuring the input-output relationships such as in ZeroCoin~\cite{zerocoin}, CoinJoin~\cite{coinjoin}. Some solutions such as MimbleWimble~\cite{mw} and Zcash~\cite{zcash,zcash1} combine both approaches. +%In fact, MW can be thought of as a combination of CT and OWAS. + +In this work, we describe \algname, another privacy enhancing protocol based on the latter approach of obscuring the input-output relationships, while keeping the amounts public. This allows us to avoid expensive range proofs necessary for the first approach. Our protocol is motivated from ZeroCoin and CoinJoin in order to overcome some of their limitations, and can be thought of as a combination of the two. + + +\section{Background} + +Bitcoin~\cite{Nak08}, ZeroCoin~\cite{zerocoin}, Zcash~\cite{zcash} and Ergo~\cite{ergo} are based on the idea of ``coins'' which are short-lived immutable data structures, often called UTXOs (short for {unspent transaction outputs}). \algname also uses UTXOs. + +\subsection{UTXO Blockchains} +In UTXO-based blockchains, every node maintains an in-memory database of all current UTXOs, called the {\em UTXO-set}. A transaction consumes (destroys) some UTXOs and creates new ones. When a node receives a block, it updates its UTXO-set based on the transactions in that block. A UTXO is a single-use object, and its simplest form contains a public key (in which case, the UTXO can be ``spent'' using the corresponding private key). Spending a UTXO essentially involves executing any embedded code inside it and removing it from the UTXO-set. UTXOs can be likened to physical coins, which get passed around between people and one person may hold multiple coins at any time (one private key can control multiple unspent UTXOs). +The alternative to UTXOs is the {\em account}-based model of Ethereum~\cite{wood2014ethereum}. Unlike UTXOs, accounts are long-lived and mutable. Each private key controls exactly one account, which can be likened to real-world bank accounts. While a UTXO must be completely spent (i.e., its balance cannot be changed), an account at the bare minimum allows changing the balance. Most privacy techniques including CoinJoin and ZeroCoin are designed for UTXOs and cannot be easily adapted for accounts. Our protocol also works in the UTXO model only. %and leaks information if used in the account-based model. + +\subsection{Guard Scripts} +A UTXO is protected by a {\em guard script}, a computer program encoding a {\em proposition}. The proposition defines the set of conditions that must be satisfied when spending the UTXO. The spender supplies a {\em proof} of satisfying the proposition. For example, the proposition may require that the entire transaction's bytes (sans the proof section) must be signed under some public key. The proof is then the signature on the transaction's bytes generated using the corresponding private key. In fact, this is the most common scenario in all UTXO systems. +The guard script can encode arbitrarily complex conditions as long as they can be encoded in the underlying language. +Guard scripts are also called smart contracts. A {\em box} is another name for a UTXO in Ergo~\cite{ergo} and we will use these two terms interchangeably. + +\subsection{Execution Context} +\label{context} + +In UTXO blockchains, a block contains a compact section called the {\em header}, which is +enough to verify the block solution and check integrity of other sections (such as block transactions). +The execution context (or simply the context) is the information available to a smart contract during execution. +To encode \algname in smart contracts (rather than within the protocol), the underlying scripting language must support a sufficiently rich context. + +We can classify the context based on what part of the block a smart contract can access. At the bare minimum, the first level, the smart contract should have access to the contents of the UTXO it guards (i.e., its monetary value and any other data stored in it). At the second level, the smart contract may additionally have access to the entire spending transaction, that is, all its inputs and outputs. At the third level, the smart contract may have access to block header data in addition to the data at the second level. For example, in \langname (which operates at this level), the last ten block headers and part of the next block header that is known in advance are also available in the execution context. Finally, at the fourth level, the execution context may contain the entire block with all sibling transactions. Note that since the execution context must fit into random-access memory of commodity hardware, accessing the full blockchain is not a realistic scenario. The following + table summarizes possible execution context components. + +~\\ +\begin{tabular}{|l|l|l|l|l|l|}\hline + Context level & UTXO & Transaction & Header & Block & Example \\ \hline + C1 & Yes & No & No & No & Bitcoin~\cite{Nak08} \\ + C2 & Yes & Yes & No & No & -- \\ + C3 & Yes & Yes & Yes & No & \langname ~\cite{ergo} \\ + C4 & Yes & Yes & Yes & Yes & -- \\\hline +\end{tabular} + +\subsection{CoinJoin} +\label{coinjoin} +CoinJoin~\cite{coinjoin} is a privacy enhancing protocol where multiple parties provide inputs and create outputs in a single transaction computed interactively such that the original inputs and outputs are unlinked. The optimal use of CoinJoin is when two inputs of equal value are joined to generate two outputs of equal value, and the process is repeated, as depicted in Figure~\ref{fig:coinjoin}. + +\begin{figure}[h] + \centering + \begin{subfigure}{.6\textwidth} + \centering + \includegraphics[width=\linewidth]{CoinJoin.jpg} + \end{subfigure}% + \caption{Canonical Multi-stage CoinJoin} + \label{fig:coinjoin} +\end{figure} + +In this model, each CoinJoin transaction has exactly two inputs (the boxes at the tail of the arrows) and two outputs (the boxes at the head of the arrows). Creating such a transaction requires a private off-chain interaction between the two parties supplying the inputs, which is denoted by the dashed line. We will ignore fee for now and revisit this issue in Section~\ref{fee}. + +The key idea of CoinJoin is that the two output boxes are {\em indistinguishable} in the following sense. +\begin{enumerate} + \item The owner of each input box controls exactly one output box. + \item An outsider cannot guess with probability better than 1/2, which output corresponds to which input. +\end{enumerate} + +Thus, each CoinJoin transaction provides 50\% unlinkability. The output box can be used as input to further CoinJoin transactions and the process repeated to increase the unlinkability to any desired level. We will use the same concept in \algname. +CoinJoin requires two parties to interactively sign a transaction off-chain and this interactive nature is the primary drawback of CoinJoin, which \algname aims to overcome. + +\subsection{ZeroCoin} +\label{zerocoin} + +ZeroCoin is a privacy enhancing protocol depicted in Figure~\ref{fig:zerocoin}. + +\begin{figure}[h] + \centering + \begin{subfigure}{.8\textwidth} + \centering + \includegraphics[width=\linewidth]{ZeroCoin.jpg} + \end{subfigure} + \caption{ZeroCoin protocol} + \label{fig:zerocoin} +\end{figure} + +The protocol uses a mixing pool (called the {\em unspent-box pool}, or simply the U-pool), to which an ordinary coin is added as a commitment $c$ to secrets $(r, s)$. The coin is later spent such that the link to $c$ is not publicly visible. The value $c$ must be permanently stored in the U-pool, since the spending transaction cannot reveal it. Instead, it reveals the secret $s$ (the {\em serial number}) along with a zero-knowledge proof that $s$ was used in a commitment from the pool. To prevent double spending, the serial number is also stored in another space called the {\em spent-box pool} (the S-pool). A coin can be spent from the U-pool only if the corresponding serial number does not exist in the S-pool. + +One consequence of this is that both the U-pool (the set of commitments) and the S-pool (the set of spent serial numbers) must be maintained in memory for verifying every transaction. +Another consequence is that the sizes of the these two sets increase monotonically. +This is the main drawback of ZeroCoin (also ZCash~\cite{zcash}), which \algname tries to address. +In \algname, once a box is spent, no information about it is kept in memory, and in particular no data sets of monotonically increasing sizes are maintained. + +Considering the addition of a coin to the mix as a deposit and removal as a withdraw, the in-memory storage in \algname is proportional to the number of deposits minus the number of withdraws, while that in ZeroCash is proportional to the number of deposits plus the number of withdraws. + +\subsection{$\Sigma$-Protocols} + +\algname uses two-party interactions called $\Sigma$-protocols defined over a cyclic multiplicative group $G$ of prime order $q$ such that the decision Diffie-Hellman (DDH) problem in $G$ is hard. Specifically, is uses two such protocols. The first, denoted \texttt{proveDlog($u$)}, is a {\em proof of knowledge of Discrete Logarithm} of some group element $u$ with respect to a fixed generator $g$. That is, the prover proves knowledge of $x$ such that $u = g^x$ by using Schnorr signatures~\cite{Sch91}. See Appendix~\ref{intro:sigma} for an overview of the protocol. + +The second primitive, denoted \texttt{proveDHTuple($g, h, u, v$)}, is a {\em proof of knowledge of Diffie-Hellman Tuple}, where the prover proves knowledge of $x$ such that $u = g^x$ {\em and} $v = h^x$ for a arbitrary generators $g$ and $h$. This is essentially two instances of the first protocol running in parallel as follows. + +\begin{enumerate} + \item The prover picks $r \stackrel{R}{\leftarrow} \mathbb{Z}_q$ and computes $(t_0, t_1) = ({g}^r, {h}^r)$. It sends $(t_0, t_1)$ to the verifier. + \item The verifier picks $c \stackrel{R}{\leftarrow} \mathbb{Z}_q$ and sends $c$ to prover. + \item The prover sends $z = r + cx$ to the verifier. + \item The verifier accepts iff ${g}^z = {t_0}\cdot {u}^c$ and $h^z=t_1\cdot v^c$. +\end{enumerate} + +We use the non-interactive variant of the above protocol obtained via the Fiat-Shamir transform, where $c = H(t_0 \Vert t_1\Vert m)$ for some message $m$ to be signed. Observe that \texttt{proveDHTuple} requires 4 exponentiations for verification, while \texttt{proveDlog} requires 2. + +\langname supports both the protocols, and thus has all the primitives needed to implement \algname. + +\section{\algname Protocol} + +\algname uses a pool of {\em Half-Mix} boxes, which are boxes ready for mixing. The set of all unspent Half-Mix boxes is called the {\em H-pool}. To mix an arbitrary box $B$, one of the following is done: +\begin{enumerate} + \item \textbf{Pool:} Add $B$ to the H-pool (by converting it to a Half-mix box) and wait for someone to mix it. + \item \textbf{Mix:} Pick any box $A$ from the H-pool and a secret bit $b$. Spend $A, B$ to generate two {\em Fully Mixed} boxes $O_0, O_1$ such $O_b$ and $O_{1-b}$ are spendable by $A$'s and $B$'s owners respectively. +\end{enumerate} + +Privacy comes from the fact that boxes $O_b$ and $O_{1-b}$ are indistinguishable so an outsider cannot guess $b$ with probability better than 1/2. Thus, the probability of guessing the original box after $n$ mixes is $1/2^n$. A box is mixed several times for the desired privacy. The protocol is depicted in Figure~\ref{fig:mixname}. + +\begin{figure}[h] + \centering + \begin{subfigure}{.6\textwidth} + \centering + \includegraphics[width=\linewidth]{ErgoMix} + \end{subfigure} + \caption{Multi-round \algname} +\label{fig:mixname} +\end{figure} + + +\subsection{One \algname Round} +Each individual \algname round consists of two stages, the {\em pool} followed by the {\em mix} stage. +Without loss of generality, Alice will pool and Bob will mix. +Let $g$ be some generator of $G$ that is fixed beforehand. Each box is assumed to have two optional registers $\alpha, \beta$ that can store elements of $G$. + +\begin{enumerate} + \item \textbf{Pool:} To add a coin to the H-pool, Alice picks secret $x\in \mathbb{Z}_q$ and computes $u = g^x$. She then creates an output box $A$ protected by a script that requires the spending transaction to contain two output boxes $O_0, O_1$ satisfying the following conditions: + \begin{enumerate} + \item Both contain the same value as $A$. + \item Both are protected by the script: $$\texttt{proveDHTuple}(g, \alpha, u, \beta) \lor \texttt{proveDlog}(\beta)$$ + \item The registers $(\alpha, \beta)$ of $O_b$ and $O_{1-b}$ contain pairs $(c, d)$ and $(d, c)$ respectively for some $c, d\in G$. + \item One of $(g, u, c, d)$ or $(g, u, d, c)$ must be a valid Diffie-Hellman tuple, that is, of the form $(g, g^x, g^y, g^{xy})$. This is encoded %in \langname + as: $$\texttt{proveDHTuple}(g, u, c, d)\lor \texttt{proveDHTuple}(g, u, d, c)$$ + \end{enumerate} + She waits for Bob to join the protocol, who will do so by spending $A$. + + + \item \textbf{Mix:} Bob picks secrets $(b, y) \in \mathbb{Z}_2\times \mathbb{Z}_q$ and obtains $u$ from the script. He then computes $h= g^y$ and $v = u^y$. He spends $A$ with one or more of his own boxes to create two output boxes $O_0, O_1$ of equal value such that $O_b$ is spendable by Alice alone and $O_{1-b}$ by Bob alone: + \begin{enumerate} + \item Registers $(\alpha, \beta)$ of $O_b$ and $O_{1-b}$ store $(h, v)$ and $(v, h)$ respectively. + % If the DDH problem in $G$ is hard, then given $(g, g^x)$ the distributions $({g}^{y}, {g}^{xy})$ and + % $({g}^{xy}, {g}^{y})$ are computationally indistinguishable. In other words, without knowledge of $x$ or $y$, one cannot guess $b$ with probability better than $1/2$. + \item $O_b, O_{1-b}$ are protected by the script: $$\texttt{proveDHTuple}(g, \alpha, u, \beta)\lor \texttt{proveDlog}(\beta)$$ + such that the box must be spent using a $\Sigma$-$\ornode$ proof (see Appendix~\ref{sigma-or}). + \end{enumerate} +\end{enumerate} +After the mix, Alice and Bob can spent their respective boxes using their secrets. +Alice can identify her box as the one with $\beta = \alpha^x$. + +\subsection{Analysis} + +For correctness, Alice requires that she is always able to spend the coin. That is, Bob should not be able to spend $A$ in a manner that makes the resulting output(s) unspendable by Alice. + +Let $x$ be the Alice's secret corresponding to her box $A$. First note that due to the clause +\texttt{proveDHTuple($g, u, c, d$) || proveDHTuple($g, u, d, c$)}, Bob has no choice but to create two outputs $O_0, O_1$ such that the registers $(\alpha, \beta)$ of $O_b$ and $O_{1-b}$ contain $(g^y, g^{xy})$ and $(g^{xy}, g^y)$ respectively for some integer $y$ and bit $b$. This implies that that $O_b$'s spending condition reduces to: +$$\texttt{proveDHTuple}(g, g^{y}, g^x, g^{xy}) \lor \texttt{proveDlog}(g^{xy}).$$ +The above statement can be proved by anyone who knows at least one of $x$ or $xy$. +Thus, Alice can spend this because only she knows $x$. + +For soundness, Alice requires that no one else should be able to spend $O_b$, her Full-Mix box. Observe that the only way someone else could spend this box is by knowing $xy$, because they don't know $x$. Assume there is an algorithm that takes as input $g^x$ and somehow outputs $(g^y, g^{xy}, xy)$ for some $y \neq 0$. Then Alice can use this to immediately compute $y$, contradicting the fact that the discrete log problem in $G$ is hard. Thus, no one must know $xy$ and thereby, only Alice has the ability to spend $O_b$. + +From Bob's point of view, the spending condition of $O_{1-b}$ reduces to +$$\texttt{proveDHTuple}(g, g^{xy}, g^x, g^{y}) \lor \texttt{proveDlog}(g^{y}).$$ +Since Bob knows $y$, he can spend the box using the right part of the statement. Finally, if someone apart from Bob spends $O_{1-b}$ then they must have used the left part of the statement because using the right part would required knowledge of $y$. However, using the left part is not possible because $(g, g^{xy}, g^x, g^{y})$ is not a valid DH Tuple. Hence, assuming that the original Schnorr proof is sound, no one else apart from Bob can spend $O_{1-b}$. + +For privacy, the only difference between $O_b$ and $O_{1-b}$ is that $(g, \alpha, u, \beta)$ is a valid Diffie-Hellman tuple for $O_b$, while for $O_{1-b}$, the tuple is $(g, \beta, u, \alpha)$. Assuming that the Decision Diffie-Hellman (DDH) problem in $G$ is hard, no one apart from Alice or Bob has the ability to distinguish the boxes. +Thus, the boxes are indistinguishable before they are spent. To see that they remain so after being spent, observe that the protocol provides {\em spender indistinguishability} because each box is spent using a $\Sigma$-OR-proof that is zero-knowledge~\cite{Dam10}. + +\paragraph{Comparing with CoinJoin:} Referring to Section~\ref{coinjoin}, both CoinJoin and \algname use the technique of spending two boxes to create two indistinguishable boxes that provide the privacy. However, CoinJoin requires the owners of the two input boxes to perform an off-chain interaction over a private channel. In \algname, this interaction is replaced by a public transaction on the blockchain. While this adds one more transaction, it does not require interaction between the parties over a private channel. This makes \algname far more usable compared to CoinJoin. Note that \algname transactions are detectable, while CoinJoin transactions are indistinguishable from ordinary transactions. + +\paragraph{Comparing with ZeroCoin:} Referring to Section~\ref{zerocoin}, both ZeroCoin and \algname use a pool (the H-pool in \algname and the U-pool in ZeroCoin). Additionally, both use zero-knowledge proofs to spend boxes from the pool in a way that hides the links between the outputs and the inputs, thereby providing the privacy. +The difference is that the privacy in \algname is achieved in stages, two boxes at a time, while in ZeroCoin it is achieved in one stage. Secondly, the degree of privacy in ZeroCoin depends on the size of the U-pool, while that in \algname depends on the number of stages. Thirdly, the U-pool in ZeroCoin increases monotonically in size, since boxes are only added to it and never removed. The size of H-pool in \algname varies according to how many unspent Half-Mix boxes are present at any given time. Finally, the proof in ZeroCoin are quite large compared to those in \algname. The last two capture the main drawbacks of ZeroCoin that \algname addresses. +In particular, using \algname, we can obtain an equivalent (or sufficiently close) degree of privacy as from ZeroCoin without having to maintain the ever-increasing pools and with much shorter proofs. + +\paragraph{Offchain Pool:} The H-Pool can be kept entirely offchain, so that Alice's Half-Mix box need not be present on the blockchain till the time Bob decides to spend it. Alice sends her unbroadcasted transaction directly to Bob who will broadcast both transactions at some later time. + + +\paragraph{Future enhancements:} Compared to CoinJoin, \algname requires an additional box -- the Half-Mix box -- as can be seen by by comparing Figures~\ref{fig:coinjoin} and~\ref{fig:mixname}. One way to eliminate the extra box would be to have the mix step output two indistinguishable Half-Mix boxes that can be used again in the mix step (or spent elsewhere). We could do this inefficiently by accumulating the statements and making the proof size proportional to the number of mixes. It is an open question if this can be done with a sublinear proof size. + +\subsection{Implementing \algname In \langname} +\label{impl} +Each round of \algname is a two-stage protocol (pool followed by mix). One way to implement \algname would be to create a specialized blockchain just for this purpose that has the protocol hardwired (as was done in, for example, ZeroCash~\cite{zcash}). However, this limits the use of the blockchain. + +A more pragmatic approach would be to encode the protocol as a smart contract on top of a general-purpose blockchain. +One such blockchain is Ergo~\cite{ergo}, whose scripting language \langname supports level C3 context (see Section~\ref{context}). Our example is implemented on top of Ergo~\cite{ergomix-impl}. +The implementation uses the concepts from \cite{multistage} by encoding \algname as a two-stage protocol such that a `fingerprint' of the second stage is embedded within a smart contract from the first stage. +In other words, Alice's box $A$ encodes the protocol by enforcing the structure of Bob's spending transaction. +This is done as follows. For brevity, we will assume that \texttt{alpha}, \texttt{beta}, \texttt{gamma} are aliases for the first, second and third registers of a box that contain elements of $G$. The keywords \texttt{script} and \texttt{value} refer to the guard script (in binary format) and the quantity of primary tokens stored in the box. + +Let $x$ be Alice's secret and let $u = g^x$. To create the Half-Mix box with $u$, first define the script of the second stage, \texttt{fullMixScript} as: +{\small +\begin{Verbatim}[frame=single] +// Contract #1: contract for Full-Mix box (fullMixScript) +proveDHTuple(g, beta, alpha, gamma) || proveDlog(gamma) +\end{Verbatim} +} +Then compile the script and compute the hash of the result: + +{\small +\begin{verbatim} +fullMixScriptHash = hash(compile(fullMixScript)) +\end{verbatim} +} +Next create a script, \texttt{halfMixScript}, with the following code: +{\small +\begin{Verbatim}[frame=single] +// Contract #2: contract for Half-Mix box (halfMixScript) +INPUTS(0).id == id && +OUTPUTS(0).beta == OUTPUTS(1).gamma && +OUTPUTS(0).gamma == OUTPUTS(0).beta && +OUTPUTS(0).alpha == alpha && OUTPUTS(1).alpha == alpha && +OUTPUTS(0).value == value && OUTPUTS(1).value == value && +hash(OUTPUTS(0).script) == fullMixScriptHash && +hash(OUTPUTS(1).script) == fullMixScriptHash && +beta != gamma && +(proveDHTuple(g, alpha, OUTPUTS(0).beta, OUTPUTS(0).gamma) || + proveDHTuple(g, alpha, OUTPUTS(0).gamma, OUTPUTS(0).beta)) +\end{Verbatim} +} + +Note that in \langname, \texttt{OUTPUTS(0)} is the first output of the spending transaction, \texttt{OUTPUTS(1)} is the second output, and so on. The keyword \texttt{id} refers to the globally unique identifier of the box. + +Alice's Half-Mix box $A$ is protected by \texttt{halfMixScript} given above. Alice must store $u$ in register \texttt{alpha} of that box. + + +\section{\mixname: \algname with Fee} +\label{fee} + +Similar to ZeroCoin and CoinJoin (Figure~\ref{fig:coinjoin}), each Half-Mix and Full-Mix box in \algname must hold the same fixed value, which is carried over to the next stage. This implies zero-fee transactions because any fee must either be paid from the Full/Half-mix boxes (which breaks the fixed value requirement) or from a non-\algname box (which breaks privacy). Zero-fee transactions are fine in theory but not in practice. %Thus, any practical implementation of \algname must somehow handle fee. + +Here we describe how to handle fee on the Ergo blockchain. To differentiate the generic protocol (\algname) from the underlying implementation using Ergo, we give the name {\em \mixname} to any of the various extensions in this section that are largely specific to Ergo. + +We can classify \algname transactions into the following types: + +\begin{enumerate} + \item \textbf{Alice entry:} When someone plays the role of Alice to create a Half-Mix box (i.e., add a coin to the H-pool). The inputs to the transaction are one or more non-\mixname boxes ({\em external} boxes) and the output is one Half-Mix box. + \item \textbf{Bob entry:} When someone plays the role of Bob to spend a Half-Mix box (i.e., remove a coin from the H-pool). The other inputs of the transaction are one or more non-\mixname boxes and the outputs are two Full-Mix boxes. + \item \textbf{Alice or Bob exit:} When someone plays the role of Alice or Bob to spend a Full-Mix box and send the funds to a non-\mixname box (i.e., withdraw from the system). + \item \textbf{Alice or Bob reentry as Alice:} When someone plays the role of Alice or Bob to spend a Full-Mix box and create a Half-Mix box (i.e., send the coin back to the H-pool). The input is a Full-Mix box and the output is a Half-Mix box of the same amount. + \item \textbf{Alice or Bob reentry as Bob:} When someone plays the role of Alice or Bob to spend a Full-Mix box along with another Half-Mix box and create two new Full-Mix boxes. The input is a Half-Mix box and a Full-Mix box and the outputs are two new Full-Mix boxes. +\end{enumerate} + +These are depicted in Figure~\ref{fig:ergomixflow}. + +\begin{figure}[h] + \centering + \begin{subfigure}{0.7\textwidth} + \centering + \includegraphics[width=\linewidth]{ErgoMixFlow.jpg} + \end{subfigure} + \caption{\algname Flow} +\label{fig:ergomixflow} +\end{figure} + +Clearly, for both Alice and Bob entries, fee is not an issue because both parties can fund the fee component of the transaction from a known source, since these transactions are not meant to hide any information. Similarly for case 3, when exiting the system, part of the amount in the Full-Mix box can be used to pay fee. The only time we need to hide the source of fee is when we spend a Full-Mix box and want to reenter as either Alice or Bob. + +\subsection{An Altruistic Approach} +\label{donation} +% \subsection{Donation-Based Fee} + +In this approach, fee is paid by a {\em sponsor} when spending a Full-Mix box for reentry. +We use a variation of {\em Fee-Emission boxes} presented in~\cite{Fee-Emission}. + +\subsubsection{Fee-Emission Box} +A sponsor creates several Fee-Emission boxes to pay fee for spending full-mix boxes in the two reentry cases above. A Fee-Emission box can be spent under the following conditions: + +\begin{enumerate} + \item There is exactly one Fee-Emission box as input. + \item There is exactly one Full-Mix box as input. + \item Either exactly one input or exactly one output is a Half-Mix box. + \item The updated balance is stored in a new Fee-Emission box. +\end{enumerate} + +This is encoded in \langname as follows: + +{\small +\begin{Verbatim}[frame=single] +// Contract #3: contract for Fee-Emission box +def isFull(b:Box) = hash(b.script) == fullMixScriptHash +def isHalf(b:Box) = hash(b.script) == halfMixScriptHash +def isFee(b:Box) = hash(b.script) == feeScriptHash +def isCopy(b:Box) = b.script == script && + b.value == value - fee +val asAlice = INPUTS.size == 2 && OUTPUTS.size == 3 && + isFull(INPUTS(0)) && isHalf(OUTPUTS(0)) && + isCopy(OUTPUTS(1)) && isFee(OUTPUTS(2) +val asBob = INPUTS.size == 3 && OUTPUTS.size == 4 && + isHalf(INPUTS(0)) && isFull(INPUTS(1)) && + isCopy(OUTPUTS(2)) && isFee(OUTPUTS(3)) +asAlice || asBob +\end{Verbatim} +} + +The condition \texttt{asAlice} encodes the rules of spending a Full-Mix box to emulate Alice for the next mix and create a Half-Mix box. Similarly, the condition \texttt{asBob} has the rules for spending a Full-Mix box as Bob's contribution in a mix transaction. + +This approach is intended to encourage mixing because the sponsor pays the fee when a Full-Mix box is remixed. However, note that there is no guarantee that some given Full-Mix box was actually created in a mix transaction since one can create a box with the same structure that was not created in a mix transaction. The only way to distinguish such a box from a real one is to examine the transaction that created the box. However, this information is not available in \langname. Thus, the above approach is susceptible to freeloaders who store their funds in a Full-Mix box. However, such freeloaders cannot utilize the fee to send to anyone. They must either create a Half-Mix box or spend another Half-Mix box, thereby forcing them participate in the protocol. Consequently, there is no advantage to the freeloader because he still has to pay fee to create the fake full-mix box, which he could have used to participate in the mixing. Hence, we can safely ignore the freeloading attack. Note that there are ways, as shown later, to determine within \langname if the a Full-Mix box was created in a mix transaction. + +The above approach requires someone to sponsor the mix transaction, which we call the {\em free system}. In practice we need \mixname to be self-sustaining that does not depend on sponsors, which we call the {\em paid system}. +The following section will extend the free system to a paid system. + +\subsection{Mixing Tokens} + +Ergo's primary token is known as {\em Erg}, which is necessary to pay for transaction fees and storage rent~\cite{chepurnoy2018systematic}. An Ergo transaction conserves primary tokens (they can neither be created nor destroyed) and any box must have a positive quantity of primary tokens. +Each box can optionally have {\em secondary tokens} which are uniquely identified by an id. Unlike primary tokens, an Ergo transaction can destroy secondary tokens. Additionally, each transaction can also create (i.e., {\em issue}) at most one new token in arbitrary quantity, whose id is the globally unique id of the first input box box of that transaction. + +In this approach, we will still use a Fee-Emission box (as in Section~\ref{donation}) to pay the fee in Ergs. However, we will also use secondary tokens issued by the creator of the Fee-Emission box, which we call {\em mixing tokens} (identified by \texttt{tokenId}). The Fee-Emission box can only be used by destroying a mixing token. + +\subsubsection{Approximate Fairness} + +We use the approximate fairness strategy described in~\cite{advtutorial}. At a high level the idea is as follows. +Each mix transaction consumes one mixing token, which must be supplied by the inputs. Thus, there must be at least one mixing token among the inputs. Additionally, to keep the outputs indistinguishable, each must have the same number of tokens. The approximate fairness strategy says that Bob must supply half the token, and is allowed to supply less tokens than Alice as long as both started with the same amount of tokens and Bob lost tokens in sequential mixes. + +The approximate-fairness strategy works only if two conditions are satisfied. The first is that mixing tokens are confined within the system by restricting their transfer to only those boxes that participate in a remix. +The second is to ensure that tokens always enter the system in a fixed quantity, and that too in one of the two \mixname boxes. + +\subsection{Token Confinement} + +In this section we enforce the first requirement of approximate fairness, that of confining the tokens within the system. +Recall that the Half-Mix box's script refers to the Full-Mix box's script via the constant \texttt{fullMixScriptHash}. Our approach additionally requires the Full-Mix box's script to refer back to the Half-Mix box's script. We do this by storing the hash of the Half-Mix script in one of the registers of the Full-Mix box. Let \texttt{delta} be an alias for this register that stores an array of bytes. The scripts are also modified. + +\subsubsection{Fee-Emission Box} +We modify \texttt{isFull} method of the Fee-Emission box contract of Section~\ref{donation} as: + +{\small +\begin{Verbatim}[frame=single] +// Contract #4: contract for Fee-Emission box +def isFull(b:Box) = hash(b.script) == fullMixScriptHash && + b.delta == halfMixScriptHash +(... remaining code same as Contract #3) +\end{Verbatim} +} + +Recall that the rule for spending the Fee-Emission box is to destroy one mixing token. While the above contract does not directly enforce this requirement, it does so indirectly via the Full-Mix and Half-Mix scripts discussed below. + +\subsubsection{Full-Mix Box} + +Modify \texttt{fullMixScript}, the contract of the Full-Mix box as follows: +{\small +\begin{Verbatim}[frame=single] +// Contract #5: contract for Full-Mix box +def isHalf(b:Box) = hash(b.script) == delta && + b.value == value +def isFull(b:Box) = b.script == script && + b.delta == delta && b.value == value +def noToken(b:Box) = b.tokens(tokenId) == 0 +val nextAlice = isHalf(OUTPUTS(0)) && INPUTS(0).id == id +val nextBob = isHalf(INPUTS(0)) && INPUTS(1).id == id +val destroyToken = OUTPUTS.forall(noToken) +val nextAliceLogic = OUTPUTS(0).tokens(tokenId) == + INPUTS(0).tokens(tokenId) - 1 && + OUTPUTS(0).tokens(tokenId) > 0 + +((nextAlice && nextAliceLogic) || nextBob || destroyToken) +&& (... earlier condition from Contract #1) +\end{Verbatim} +} + +The script enforces the transfer of mixing tokens when spending the Full-Mix box to create a Half-Mix box. In particular, the tokens can only be transferred if the transaction either outputs a Half-Mix box (i.e., the spender takes the role of Alice in the next mix step, in which case one mixing token is destroyed) or participates in a mix transaction as Bob and spends a Half-Mix box along with this Full-Mix box (in which case, the transfer of mixing tokens is governed by the contract in the Half-Mix box). +% The usage of mixing tokens when spending the Full-Mix box in a mix transaction is enforced in the Half-Mix box. In the remaining cases the mixing tokens must be destroyed. + +\subsubsection{Half-Mix Box} +Next, the Half-Mix box (\texttt{halfMixScript}) contract is also modified: +{\small +\begin{Verbatim}[frame=single] +// Contract #6: contract for Half-Mix box +val alice = INPUTS(0).tokens(tokenId) +val bob = INPUTS(1).tokens(tokenId) +val out0 = OUTPUTS(0).tokens(tokenId) +val out1 = OUTPUTS(1).tokens(tokenId) +val tLogic = alice + bob == out0 + out1 + 1 && bob > 0 && alice > 0 + +OUTPUTS(0).delta == hash(script) && +OUTPUTS(1).delta == hash(script) && out0 == out1 && tLogic && +&& (... earlier condition from Contract #2) +\end{Verbatim} +} + +The above contract assumes that the boxes already have some quantity of mixing tokens and enforces how these must be used. Each mix transaction is assumed to consume one such token, and to maintain privacy, the token balance must be equally distributed between the two outputs. The contract follows the {\em approximate-fairness} strategy where Alice requires Bob to contribute at least one mixing token~\cite{advtutorial}. It is possible to use {\em perfect fairness} by adding the condition \texttt{alice == bob}. + +Taken together, the contracts of the three boxes (Fee-Emission, Full-Mix and Half-Mix) {\em confine} the mixing tokens to the system, which comprises of sequential rounds of \mixname. In other words, once the mixing tokens have entered the system, they must remain within it. + +\subsection{Token Entry} + +While the earlier section restricted how the mixing tokens are transferred after they are already in the system, this section will focus on defining how the tokens enter the system. This is done using a {\em Token-Emission box}. + +\subsubsection{Token-Emission Box} + +A Token-Emission box is used for obtaining mixing tokens and gain entry into the system as either Alice or Bob. It contains the following contract. + +{\small +\begin{Verbatim}[frame=single] +// Contract #7: contract for Token-Emission box +def isCopy(b:Box) = b.script == script && b.value == value && + b.tokens(tokenId) == tokens(tokenId) - amt +def isFull(b:Box) = hash(b.script) == fullMixScriptHash && + b.delta == halfMixScriptHash +def isHalf(b:Box) = hash(b.script) == halfMixScriptHash +def isFee(b:Box) = hash(b.script) == feeScriptHash && + b.value == fee +def isEntry(b:Box) = (isFull(b) || isHalf(b)) && + b.tokens(tokenId) == amt +def isZero(b:Box) = b.tokens(tokenId) == 0 + +INPUTS(0).id == id && isZero(INPUTS(1)) && INPUTS.size == 2 && +isEntry(OUTPUTS(0)) && isCopy(OUTPUTS(1)) && isFee(OUTPUTS(2)) +\end{Verbatim} +} + +% The Token-Emission box +% is the final piece of the puzzle and +% encodes the following logic. +Anyone can spend the Token-Emission box to send a fixed amount \texttt{amt} of mixing tokens to either a Half-Mix box or a (fake) Full-Mix box, which should be the first output of the transaction. The other outputs are a copy of the token-emission box with the balance tokens and the fee paying output. The transaction must have exactly two inputs, with the token-emission box being the first and the second containing zero mixing tokens. + +We can use mixing tokens to verify that a given Full-Mix box was indeed created in a mix transaction, and a given Half-Mix box was indeed created by spending a Full-Mix box. In particular, this is true if and only if the box contains less than \texttt{amt} and more than 0 mixing tokens. + +While the above Token-Emission box gives the mixing tokens for free, it is trivial to modify the contract to sell the tokens at some given rate. The only change required is in the \texttt{isCopy} method: + +{\small +\begin{Verbatim}[frame=single] +// Contract #8: contract for Token-Emission box +def isCopy(b:Box) = b.script == script && + b.value == value + amt * rate && + b.tokens(tokenId) == tokens(tokenId) - amt +(... remaining code same as Contract #7) +\end{Verbatim} +} + +We also want the token issuer to be able to withdraw any Ergs deposited by token buyers. To achieve this, the token-emission box is again modified: + +{\small +\begin{Verbatim}[frame=single] +// Contract #9: contract for Token-Emission box +(... earlier condition from Contract #8) || +(issuerPubKey && INPUTS.size == 1 && + OUTPUTS(0).script == script && OUTPUTS(0).value > minErgs && + OUTPUTS(0).tokens(tokenId) == tokens(tokenId)) +\end{Verbatim} +} +It is necessary to keep a certain amount of Ergs, \texttt{minErgs} inside each Token-Emission box, otherwise the box may be destroyed when miners collect storage rent. This value should be large enough to ensure sustenance for several years. +In order to allow several people to buy tokens in the same block and to avoid collisions when multiple people try to spend the same token-emission box, there must be several token-emission boxes. + +\subsubsection{Analysis} + +Because of the condition \texttt{bob > 0} in \texttt{tLogic} of the Half-Mix box, a mix transaction requires Bob to supply at least one token, and since these tokens can only be stored in either Full or Half-Mix boxes, the second input of a mix transaction must be a Full-Mix box (as opposed to any box). +That Full-Mix box can either be the output of a mix transaction (a real Full-Mix box) or the output of a token purchase transaction (a fake Full-Mix box). + +Another consequence of \texttt{bob > 0} is that at least one token must exist in order to spend Alice's box. In the case that mixing tokens become unavailable, Alice's box is rendered unspendable. In order to handle this, we need to ensure that mixing tokens are always available. One way would be to have each token-emission box store a large number of tokens, much more than what can be purchased with all the available Ergs. Before storing any funds in a Half-Mix box, it must be ascertained that there are a large number of mixing tokens stored in at least one token-emission box. + +An alternate way to ensure that Alice's Half-Mix box does not get stuck due to non-availability of tokens would be to allow Alice to spend the box using her secret. This requires modifying the Half-Mix box as follows: + +{\small +\begin{Verbatim}[frame=single] +// Contract #10: contract for Half-Mix box +def noToken(b:Box) = b.tokens(tokenId) == 0 + +(proveDlog(alpha) && INPUTS.size == 1 && OUTPUTS.forall(noToken)) || +(... earlier condition from Contract #6) +\end{Verbatim} +} + +The above modification allows Alice to spend the Half-Mix box using her secret but she must destroy all mixing tokens in doing so. + +Figure~\ref{fig:mixingtokens} gives an example flow with the above contracts in place. To avoid clutter, we skipped the fee output in the above flow. However, each transaction is implicitly assumed to have an additional box for paying fee. + +\begin{figure}[h] + \centering + \begin{subfigure}{0.9\textwidth} + \centering + \includegraphics[width=\linewidth]{MixingTokens.jpg} + \end{subfigure} + \caption{Multi-round \mixname with Mixing Tokens to handle fee} +\label{fig:mixingtokens} +\end{figure} + +Comparing this with Figure~\ref{fig:ergomixflow}, a mix transaction is always a reentry as Bob and both Alice and Bob's entry is through a token purchase transaction. + +The predicate \texttt{alice > 0} also requires that the Half-Mix box have at least one token, implying that the only way to create the Half-Mix box would be in a token purchase transaction or transaction for reentry as Alice. In particular, it is impossible to create a Half-Mix box in an other manner. + +\subsubsection{Further Enhancements} + + +The approximate fairness approach requires each party to contribute half a mixing token in a mix transaction and the balance tokens are equally distributed among the parties, even if one supplied more. + +% Below we consider some enhancements to this. For brevity, we skip the \langname code and only give the high level approach. + +In the {\em First-Spender-Pays-Fee} approach, the idea is to benefit the party willing to wait longer and the party that spends their Full-Mix box first (retroactively) pays the fee for the mix transaction. This can be achieved as follows. Each mix transaction consumes two tokens. One is destroyed and the other is stored in a {\em refund box} that can be claimed by the second spender. We can identify the second spender as follows. + +\textbf{Identifying the second-spender:} Identifying if a given transaction is by the second spender is equivalent to determining if the other box is already spent. In a stateless language such as \langname, there is no direct way to determine if some other box is already spent. However, there are indirect ways and we describe one of them below. For brevity, we skip the \langname code and only give the high level approach. + +The protocol is modified to require each mix transaction to generate exactly 4 quantities of a new secondary token (with id $x$, determined as the id of the first input -- the Half-Mix box) distributed equally among 4 outputs. Two of these outputs are the standard Full-Mix output boxes $O_0, O_1$ with the additional spending condition that at least one output in the spending transaction must contain some non-zero quantity of token $x$. The other two outputs, $O_2, O_3$, have the following identical spending conditions: +\begin{enumerate} + \item The sum of quantities of token $x$ in the inputs and outputs is 3 and 2 respectively. + \item One output contains 2 quantities of token $x$ protected by the same script as this box. +\end{enumerate} + +It can be checked that the current spender is the second spender if and only if there is an input with two quantities of token $x$. The script also ensures that the first spender must create a new box with two tokens that can only be used by the second spender. + +\section*{Acknowledgements} + +We would like to thank anonymous developer {\em anon92048} for developing an implementation of the mixer, and also Jason Davies +for finding and reporting a vulnerability in both the paper and initial implementation. Vulnerability description made by Jason Davies +can be found in \cite{ergomix-vuln}. + +% \section{Conclusion} +% [...] +\bibliographystyle{unsrt} +\bibliography{main} +\appendix + +\section{$\Sigma$-Protocols} +\label{intro:sigma} +\algname uses Sigma protocols (written $\Sigma$-protocols), which are a generalization of the Schnorr identification scheme~\cite{Sch91}. + +\subsection{Schnorr Identification} + Let $G$ be a cyclic multiplicative group of prime order $q$ and $g$ a generator of $G$. Alice wants to prove knowledge of some secret $x\in \mathbb{Z}_q$ to Bob who knows $u = g^x$. Assume that computing discrete logarithms in $G$ is hard. They perform the following protocol, also known as Schnorr identification: + +\begin{enumerate} + \item \textbf{Commit:} Alice selects random $r\in \mathbb{Z}_q$ and send $t = g^r\in G$ to Bob. + \item \textbf{Challenge:} Bob selects random $c\in\mathbb{Z}_q$ and sends it to Alice. + \item \textbf{Response:} Alice sends $z = r + cx$ to Bob, who accepts iff $g^z = t\cdot u^c$. +\end{enumerate} + +The above protocol is a proof of knowledge because Bob can extract $x$ if he can get Alice to respond twice for the same $r$ and different $c$. As an example, for $c = 1, 2$, Bob can obtain $r+x$ and $r+2x$, the difference of which gives $x$. This is also called (special) soundness. The above protocol is also (honest verifier) zero-knowledge because anyone can impersonate Alice if the challenge $c$ of Step 2 is known in advance, simply by picking random $z \in\mathbb{Z}_q$ and computing $t = g^z/u^c$. The statement ``I know the discrete log of $u$ to base $g$'' is called the {\em proposition}, which we denote by $\tau$. + +Any protocol that has the above 3-move structure (Alice $\stackrel{t}{\rightarrow}$ Bob, Bob $\stackrel{c}{\rightarrow}$ Alice, Alice $\stackrel{z}{\rightarrow}$ Bob), along with zero-knowledge and soundness property is called a $\Sigma$-protocol. + +For any $\Sigma$-protocol with messages $(t, c, z)$, we can apply the Fiat-Shamir transform~\cite{fiatshamir} to convert it into a non-interactive one by replacing the role of Bob in Step 2 by any hash function $H$ and computing $c$ = $H(t)$. The resulting protocol with messages $(t, H(t), z)$ can be performed by Alice alone. Intuitively, since $t$ fixes $c$, Bob cannot ``rewind'' Alice and get two different responses for the same $r$. Additionally, Alice cannot know $c$ in advance before deciding $t$ if $H$ behaves like a random oracle. We call such a non-interactive proof a {\em $\Sigma$-proof}~\cite{Cra96}. Conceptually, $\Sigma$-proofs are generalizations of digital signatures~\cite{CL06}. + +\subsection{Composing $\Sigma$-Protocols} +\label{sigma-or} +Any two $\Sigma$-protocols of propositions $\tau_0, \tau_1$ with messages $(t_0, c_0, z_0), (t_1, c_1, z_1)$ respectively can be combined into a $\Sigma$-protocol of $\tau_0 \land \tau_1$ with messages $(t, c, z) = (t_0\Vert t_1,c_0\Vert c_1, c_0\Vert c_1)$. We call such a construction an $\andnode$ operator on the protocols. +More interestingly, as shown in \cite{CDS94},the two protocols can also be used to construct a $\Sigma$-protocol for $\tau_0\lor \tau_1$, where Alice proves knowledge of the witness of one proposition, without revealing which. Let $b\in \{0, 1\}$ be the bit such that Alice knows the witness for $\tau_b$ but not for $\tau_{1-b}$. Alice will run the correct protocol for $\tau_b$ and a simulation for $\tau_{1-b}$. First she generates a random challenge $c_{1-b}$. She then generates $(t_{1-b}, z_{1-b})$ by using the simulator on $c_{1-b}$. She also generates $t_b$ by following the protocol correctly. The pair $(t_0, t_1)$ is sent to Bob, who responds with a challenge $c$. Alice then computes $c_b = c\oplus c_{1-b}$. She computes $z_b$ using $(t_b, c_b)$. Her response to Bob is $((z_0, c_0), (z_1, c_1))$, who accepts if: (1) $c = c_0 \oplus c_1$ and (2) $(t_0, c_0, z_0), (t_1, c_1, z_1)$ are both accepting conversations for $\tau_0, \tau_1$ respectively. We call such a construction a $\ornode$ operator. + +Clearly, both the $\andnode$ and $\ornode$ operators also result in $\Sigma$-protocols that can be further combined or made non-interactive via the Fiat-Shamir transform. Crucially, the proof for $\ornode$ does not reveal which of the relevant values the prover knows. For example, in \langname a ring signature by public keys $u_1, u_2$ can be specified as an $\ornode$ of $\Sigma$-protocols for proving knowledge of discrete logarithms of $u_1$ or $u_n$. The proof can be constructed with the knowledge of just one such discrete logarithm, and does not reveal which one was used in its construction. This is a crucial property used in \algname. +\langname, the programming language of Ergo gives the ability to build sophisticated $\Sigma$-protocols using the connectives $\andnode$, $\ornode$. This allows us to implement \algname on top of Ergo using smart contracts. + + +\end{document} diff --git a/library-api/src/main/scala/special/collection/Colls.scala b/library-api/src/main/scala/special/collection/Colls.scala index 231e3fba71..e177638057 100644 --- a/library-api/src/main/scala/special/collection/Colls.scala +++ b/library-api/src/main/scala/special/collection/Colls.scala @@ -367,6 +367,12 @@ trait Coll[@specialized A] { */ def reverse: Coll[A] + /** Checks if this collection is a replication of the given value a given number of times. + * + * @param len how many times the `value` is replicated + * @param value the replicated value + * @return true if this collection is element-wise equal to a collection of replicated values. + */ @Internal private[collection] def isReplArray(len: Int, value: A): Boolean @@ -427,9 +433,26 @@ trait ReplColl[@specialized A] extends Coll[A] { @scalan.Liftable @WithMethodCallRecognizers trait CollBuilder { + /** Monoid builder associated with this collections builder. + * It should be used to create monoids which are required by some of the Coll methods. + */ def Monoids: MonoidBuilder + + /** Constructs a new collection of pairs out of the pair of collections by zipping them. + * The resulting collection is semantically equivalent to `as.zip(bs)`. + * @param as collection of first items + * @param bs collection of second items + * @return an instance of [[PairColl]] interface with represents the resulting collection of pairs. + */ def pairColl[@specialized A, @specialized B](as: Coll[A], bs: Coll[B]): PairColl[A,B] + /** Constructs a new collection of pairs out of the pair of arrays by wrapping them in collections + * and delegating to [[pairColl]] method. + * The resulting collection is semantically equivalent to as.zip(bs). + * @param as collection of first items + * @param bs collection of second items + * @return an instance of [[PairColl]] interface with represents the resulting collection of pairs. + */ @Internal def pairCollFromArrays[A: RType, B: RType](as: Array[A], bs: Array[B]): PairColl[A,B] = pairColl(fromArray(as), fromArray(bs)) @@ -471,6 +494,16 @@ trait CollBuilder { @Internal def makeView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B): Coll[B] + /** Create a new view using pre-calculated results of `f`. + * + * @param source the collection the view is based on. + * @param f view function, which transforms each pre-image element to the + * corresponding `image` element of the resulting collection + * @param calculated array of flags marking which element where pre-calculated + * @param calculatedItems pre-calculated images to be used in the resulting collection + * so that `calculated.length == calculatedItems.length` + * @return collection of images of `f` + */ @Internal def makePartialView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B, calculated: Array[Boolean], calculatedItems: Array[B]): Coll[B] @@ -492,11 +525,10 @@ trait CollBuilder { (left: Coll[(K, L)], right: Coll[(K, R)]) (l: ((K,L)) => O, r: ((K,R)) => O, inner: ((K,(L,R))) => O): Coll[(K,O)] - /** Flattens a two-dimensional array by concatenating all its rows - * into a single array. + /** Flattens a two-dimensional collection by concatenating all its rows + * into a single collection. * - * @tparam U Type of row elements. - * @param asTrav A function that converts elements of this array to rows - arrays of type `U`. + * @tparam A Type of row elements. * @return An array obtained by concatenating rows of this array. */ def flattenColl[A:RType](coll: Coll[Coll[A]]): Coll[A] diff --git a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala index 51640b2aad..bc0cc0ae28 100644 --- a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala +++ b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala @@ -184,7 +184,7 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A] } @Internal - def isReplArray(len: Int, value: A): Boolean = { + override def isReplArray(len: Int, value: A): Boolean = { length == len && { if (tItem.classTag.runtimeClass.isPrimitive) { isAllPrimValue(value) @@ -210,7 +210,14 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A] class CollOverArrayBuilder extends CollBuilder { override def Monoids: MonoidBuilder = new MonoidBuilderInst - @inline def pairColl[@specialized A, @specialized B](as: Coll[A], bs: Coll[B]): PairColl[A, B] = new PairOfCols(as, bs) + @inline override def pairColl[@specialized A, @specialized B](as: Coll[A], bs: Coll[B]): PairColl[A, B] = { + // TODO HF: use minimal length and slice longer collection + // The current implementation doesn't check the case when `as` and `bs` have different lengths. + // in which case the implementation of `PairOfCols` has inconsistent semantics of `map`, `exists` etc methods. + // To fix the problem, the longer collection have to be truncated (which is consistent + // with how zip is implemented for Arrays) + new PairOfCols(as, bs) + } @Internal override def fromMap[K: RType, V: RType](m: Map[K, V]): Coll[(K, V)] = { @@ -233,7 +240,7 @@ class CollOverArrayBuilder extends CollBuilder { @NeverInline @Reified("T") - def fromItems[T](items: T*)(implicit cT: RType[T]): Coll[T] = cT match { + override def fromItems[T](items: T*)(implicit cT: RType[T]): Coll[T] = cT match { case pt: PairType[a,b] => val tA = pt.tFst val tB = pt.tSnd @@ -243,7 +250,7 @@ class CollOverArrayBuilder extends CollBuilder { } @NeverInline - def fromArray[@specialized T: RType](arr: Array[T]): Coll[T] = RType[T] match { + override def fromArray[@specialized T: RType](arr: Array[T]): Coll[T] = RType[T] match { case pt: PairType[a,b] => val tA = pt.tFst val tB = pt.tSnd @@ -253,7 +260,7 @@ class CollOverArrayBuilder extends CollBuilder { } @NeverInline - def replicate[@specialized T: RType](n: Int, v: T): Coll[T] = RType[T] match { + override def replicate[@specialized T: RType](n: Int, v: T): Coll[T] = RType[T] match { case pt: PairType[a,b] => val tA = pt.tFst val tB = pt.tSnd @@ -264,15 +271,15 @@ class CollOverArrayBuilder extends CollBuilder { } @NeverInline - def makeView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B): Coll[B] = new CViewColl(source, f) + override def makeView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B): Coll[B] = new CViewColl(source, f) @NeverInline - def makePartialView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B, calculated: Array[Boolean], calculatedItems: Array[B]): Coll[B] = { + override def makePartialView[@specialized A, @specialized B: RType](source: Coll[A], f: A => B, calculated: Array[Boolean], calculatedItems: Array[B]): Coll[B] = { new CViewColl(source, f).fromPartialCalculation(calculated, calculatedItems) } @NeverInline - def unzip[@specialized A, @specialized B](xs: Coll[(A,B)]): (Coll[A], Coll[B]) = xs match { + override def unzip[@specialized A, @specialized B](xs: Coll[(A,B)]): (Coll[A], Coll[B]) = xs match { case pa: PairColl[_,_] => (pa.ls, pa.rs) case _ => val limit = xs.length @@ -289,7 +296,7 @@ class CollOverArrayBuilder extends CollBuilder { } @NeverInline - def xor(left: Coll[Byte], right: Coll[Byte]): Coll[Byte] = left.zip(right).map { case (l, r) => (l ^ r).toByte } + override def xor(left: Coll[Byte], right: Coll[Byte]): Coll[Byte] = left.zip(right).map { case (l, r) => (l ^ r).toByte } @NeverInline override def emptyColl[T](implicit cT: RType[T]): Coll[T] = cT match { diff --git a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala index 669d0f56bd..c4c92b7d4b 100644 --- a/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala +++ b/sigma-api/src/main/scala/special/sigma/SigmaDsl.scala @@ -2,10 +2,8 @@ package special.sigma import java.math.BigInteger -import org.bouncycastle.math.ec.custom.sec.SecP256K1Point import org.bouncycastle.math.ec.ECPoint -import scala.reflect.ClassTag import special.collection._ import scalan._ @@ -173,7 +171,7 @@ trait BigInt { /** * Returns the minimum of this BigInteger and {@code val}. * - * @param val value with which the minimum is to be computed. + * @param that value with which the minimum is to be computed. * @return the BigInteger whose value is the lesser of this BigInteger and * { @code val}. If they are equal, either may be returned. */ @@ -182,7 +180,7 @@ trait BigInt { /** * Returns the maximum of this BigInteger and {@code val}. * - * @param val value with which the maximum is to be computed. + * @param that value with which the maximum is to be computed. * @return the BigInteger whose value is the greater of this and * { @code val}. If they are equal, either may be returned. */ @@ -192,6 +190,26 @@ trait BigInt { * @return { @code -this} */ def negate(): BigInt + + /** Returns a BigInteger whose value is `(this & that)`. (This + * method returns a negative BigInteger if and only if `this` and `that` are + * both negative.) + * + * @param that value to be AND'ed with this BigInteger. + * @return `this & that` + */ + def and(that: BigInt): BigInt + @Internal def &(that: BigInt): BigInt = and(that) + + /** Returns a BigInteger whose value is `(this | that)`. (This + * method returns a negative BigInteger if and only if either `this` or `that`` is + * negative.) + * + * @param that value to be OR'ed with this BigInteger. + * @return `this | that` + */ + def or(that: BigInt): BigInt + @Internal def |(that: BigInt): BigInt = or(that) } /** Base class for points on elliptic curves. @@ -252,13 +270,19 @@ trait SigmaProp { @OverloadId("or_bool") def ||(other: Boolean): SigmaProp } +/** Represents any value paired with type descriptor. */ @scalan.Liftable @WithMethodCallRecognizers trait AnyValue { + /** The data value wrapped by this instance. */ def value: Any + /** The type descriptor of the `value` instance. */ def tVal: RType[Any] } +/** Runtime representation of Ergo boxes used during execution of ErgoTree operations. + * @see [[org.ergoplatform.ErgoBox]] + */ @scalan.Liftable @WithMethodCallRecognizers trait Box { @@ -281,10 +305,60 @@ trait Box { def registers: Coll[AnyValue] /** Extracts register by id and type. - * @param regId zero-based identifier of the register. + * ErgoScript is typed, so accessing a register is an operation which involves some + * expected type given in brackets. Thus `SELF.R4[Int]` expression should evaluate to a + * valid value of the `Option[Int]` type. + * + * For example `val x = SELF.R4[Int]` expects the + * register, if it is present, to have type `Int`. At runtime the corresponding type + * descriptor is passed as `cT` parameter. + * + * There are three cases: + * 1) If the register doesn't exist. + * Then `val x = SELF.R4[Int]` succeeds and returns the None value, which conforms to + * any value of type `Option[T]` for any T. (In the example above T is equal to + * `Int`). Calling `x.get` fails when x is equal to None, but `x.isDefined` + * succeeds and returns `false`. + * 2) If the register contains a value `v` of type `Int`. + * Then `val x = SELF.R4[Int]` succeeds and returns `Some(v)`, which is a valid value + * of type `Option[Int]`. In this case, calling `x.get` succeeds and returns the + * value `v` of type `Int`. Calling `x.isDefined` returns `true`. + * 3) If the register contains a value `v` of type T other then `Int`. + * Then `val x = SELF.R4[Int]` fails, because there is no way to return a valid value + * of type `Option[Int]`. The value of register is present, so returning it as None + * would break the typed semantics of registers collection. + * + * In some use cases one register may have values of different types. To access such + * register an additional register can be used as a tag. + * + *+ * val tagOpt = SELF.R5[Int] + * val res = if (tagOpt.isDefined) { + * val tag = tagOpt.get + * if (tag == 1) { + * val x = SELF.R4[Int].get + * // compute res using value x is of type Int + * } else if (tag == 2) { + * val x = SELF.R4[GroupElement].get + * // compute res using value x is of type GroupElement + * } else if (tag == 3) { + * val x = SELF.R4[ Array[Byte] ].get + * // compute res using value x of type Array[Byte] + * } else { + * // compute `res` when `tag` is not 1, 2 or 3 + * } + * } + * else { + * // compute value of res when register is not present + * } + *+ * + * @param i zero-based identifier of the register. * @tparam T expected type of the register. - * @return Some(value) if the register is defined and has given type. + * @return Some(value) if the register is defined AND has the given type. * None otherwise + * @throws special.sigma.InvalidType exception when the type of the register value is + * different from cT. * @since 2.0 */ def getReg[@Reified T](i: Int)(implicit cT: RType[T]): Option[T] @@ -342,6 +416,8 @@ trait Box { * * Please note that standard hash function from `scorex.crypto.hash` is used, and height is stored along with root hash of * the tree, thus `digest` size is always CryptoConstants.hashLength + 1 bytes. + * + * This interface is used as runtime representation of the AvlTree type of ErgoTree. */ @scalan.Liftable trait AvlTree { @@ -533,7 +609,9 @@ trait Header { def votes: Coll[Byte] //3 bytes } -/** Represents data available in Sigma language using `CONTEXT` global variable*/ +/** Runtime representation of Context ErgoTree type. + * Represents data available in Sigma language using `CONTEXT` global variable. + */ @scalan.Liftable @WithMethodCallRecognizers trait Context { @@ -622,6 +700,16 @@ trait SigmaContract { (implicit cT: RType[T]): Coll[Byte] = this.builder.substConstants(scriptBytes, positions, newValues) } +/** Runtime representation of SGlobal ErgoTree type. + * The only instance of SGlobal type can be referenced as `Global` variable in ErgoScript. + * It is represented as [[org.ergoplatform.Global]] node of ErgoTree, which evaluates to + * the default singleton instance of this interface. + * + * CostingSigmaDslBuilder object serves as the default singleton instance of Global + * object, which implements global ErgoTree functions. + * + * @see SGlobal.WrappedType, CostingSigmaDslBuilder + */ @scalan.Liftable @WithMethodCallRecognizers trait SigmaDslBuilder { diff --git a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala index f14ba44ccd..d092fb40a1 100644 --- a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala +++ b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala @@ -38,7 +38,10 @@ class TestSigmaDslBuilder extends SigmaDslBuilder { def anyZK(props: Coll[SigmaProp]): SigmaProp = MockSigma(props.exists(p => p.isValid)) @NeverInline - override def xorOf(conditions: Coll[Boolean]): Boolean = conditions.toArray.distinct.length == 2 + override def xorOf(conditions: Coll[Boolean]): Boolean = { + // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 + conditions.toArray.distinct.length == 2 + } @NeverInline def sigmaProp(b: Boolean): SigmaProp = MockSigma(b) diff --git a/sigma-impl/src/main/scala/special/sigma/TestBigInt.scala b/sigma-impl/src/main/scala/special/sigma/TestBigInt.scala index 595e9cc2ec..b23cea1e81 100644 --- a/sigma-impl/src/main/scala/special/sigma/TestBigInt.scala +++ b/sigma-impl/src/main/scala/special/sigma/TestBigInt.scala @@ -49,4 +49,8 @@ abstract class TestBigInt(private[sigma] val value: BigInteger) extends BigInt { override def max(that: BigInt): BigInt = dsl.BigInt(value.max(that.value)) override def negate(): BigInt = dsl.BigInt(value.negate().to256BitValueExact) + + override def and(that: BigInt): BigInt = dsl.BigInt(value.and(that.value)) + + override def or(that: BigInt): BigInt = dsl.BigInt(value.or(that.value)) } diff --git a/sigma-impl/src/main/scala/special/sigma/TestContext.scala b/sigma-impl/src/main/scala/special/sigma/TestContext.scala index bf3c98e5d3..52102112b3 100644 --- a/sigma-impl/src/main/scala/special/sigma/TestContext.scala +++ b/sigma-impl/src/main/scala/special/sigma/TestContext.scala @@ -1,9 +1,17 @@ package special.sigma +import scalan.OverloadHack.Overloaded1 import scalan.RType +// TODO refactor: move to sigmastate package and rename to CAnyValue + +/** Default implementation of AnyValue interface. */ case class TestValue[A](value: A, tVal: RType[Any]) extends AnyValue { def tA: RType[A] = tVal.asInstanceOf[RType[A]] - override def toString = s"Value($value)" + override def toString = s"TestValue($value)" } +object TestValue { + def apply[A](value: A, t: RType[A])(implicit o: Overloaded1): TestValue[A] = + new TestValue(value, t.asInstanceOf[RType[Any]]) +} diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala index 35b14e3036..6af5c4fca6 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoAddress.scala @@ -4,11 +4,11 @@ import java.util import com.google.common.primitives.Ints import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix -import scorex.crypto.hash.{Digest32, Blake2b256} +import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.encode.Base58 import sigmastate.Values._ import sigmastate._ -import sigmastate.basics.DLogProtocol.{ProveDlogProp, ProveDlog} +import sigmastate.basics.DLogProtocol.{ProveDlog, ProveDlogProp} import sigmastate.lang.exceptions.SigmaException import sigmastate.serialization._ import sigmastate.utxo.{DeserializeContext, Slice} @@ -47,14 +47,14 @@ import scala.util.Try * Address examples for testnet: * * 3 - P2PK (3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN) - * 8 - P2SH (8UmyuJuQ3FS9ts7j72fn3fKChXSGzbL9WC, 8LnSX95GAWdbDZWJZQ73Uth4uE8HqN3emJ) - * ? - P2S (imdaM2NzX, z4hAmfvfSnQJPChMWzfBzJjpB8ei2HoLCZ2RHTaNArMNHFirdJTc7E) + * ? - P2SH (rbcrmKEYduUvADj9Ts3dSVSG27h54pgrq5fPuwB) + * ? - P2S (Ms7smJwLGbUAjuWQ) * * for mainnet: * * 9 - P2PK (9fRAWhdxEsTcdb8PhGNrZfwqa65zfkuYHAMmkQLcic1gdLSV5vA) - * 2 - P2SH (25qGdVWg2yyYho8uC1pLtc7KxFn4nEEAwD, 23NL9a8ngN28ovtLiKLgHexcdTKBbUMLhH) - * ? - P2S (7bwdkU5V8, BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ) + * ? - P2SH (8UApt8czfFVuTgQmMwtsRBZ4nfWquNiSwCWUjMg) + * ? - P2S (4MQyML64GnzMxZgm, BxKBaHkvrTvLZrDcZjcsxsF7aSsrN73ijeFZXtbj4CXZHHcvBtqSxQ) * * * Prefix byte = network type + address type @@ -110,14 +110,15 @@ class Pay2SHAddress(val scriptHash: Array[Byte])(implicit val encoder: ErgoAddre override val addressTypePrefix: Byte = Pay2SHAddress.addressTypePrefix override val contentBytes: Array[Byte] = scriptHash + import Pay2SHAddress._ /** The proposition which checks that `contextVar(1)` has original script, * which evaluates to true and also whose hash equals to this `scriptHash`. * Assumes the context variable accessed as getVar[Coll[Byte]](1)` to contain serialized original script bytes. * @see ErgoLikeInterpreterSpecification."P2SH - 160 bits" test - * similar script checked in "P2SH - 160 bits" test in sigma repository, but here we use 192 bits - */ + * similar script checked in "P2SH - 160 bits" test in sigma repository, but here we use 192 bits + **/ override val script = { val hashEquals = EQ( Slice(CalcBlake2b256(GetVarByteArray(scriptId).get), IntConstant(0), IntConstant(24)), @@ -142,16 +143,18 @@ object Pay2SHAddress { val addressTypePrefix: Byte = 2: Byte /** Create Pay-to-script-hash address with the given underlying script (ErgoTree). - * @param script ErgoTree representation of guarding script - * @param encoder address encoder which is used to encode address bytes as String */ + * + * @param script ErgoTree representation of guarding script + * @param encoder address encoder which is used to encode address bytes as String */ def apply(script: ErgoTree)(implicit encoder: ErgoAddressEncoder): Pay2SHAddress = { val prop = script.toProposition(replaceConstants = script.isConstantSegregation) apply(prop) } /** Create Pay-to-script-hash address with the given underlying proposition (SigmaPropValue). - * @param prop Value representation of guarding script (aka proposition) - * @param encoder address encoder which is used to encode address bytes as String */ + * + * @param prop Value representation of guarding script (aka proposition) + * @param encoder address encoder which is used to encode address bytes as String */ def apply(prop: SigmaPropValue)(implicit encoder: ErgoAddressEncoder): Pay2SHAddress = { val sb = ValueSerializer.serialize(prop) val sbh = ErgoAddressEncoder.hash192(sb) @@ -202,6 +205,7 @@ case class ErgoAddressEncoder(networkPrefix: NetworkPrefix) { } def isTestnetAddress(addrHeadByte: Byte): Boolean = addrHeadByte > TestnetNetworkPrefix + def isMainnetAddress(addrHeadByte: Byte): Boolean = addrHeadByte < TestnetNetworkPrefix def fromString(addrStr: String): Try[ErgoAddress] = Base58.decode(addrStr).flatMap { bytes => @@ -226,12 +230,15 @@ case class ErgoAddressEncoder(networkPrefix: NetworkPrefix) { val p = GroupElementSerializer.parse(r) new P2PKAddress(ProveDlog(p), contentBytes) case Pay2SHAddress.addressTypePrefix => + if (contentBytes.length != 24) { //192-bits hash used + throw new Exception(s"Improper content in P2SH script: $addrStr") + } new Pay2SHAddress(contentBytes) case Pay2SAddress.addressTypePrefix => val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(contentBytes) new Pay2SAddress(tree, contentBytes) case _ => - throw new Exception("Unsupported address type: " + addressType) + throw new Exception(s"Unsupported address type: $addressType") } } } @@ -269,4 +276,4 @@ object ErgoAddressEncoder { def hash256(input: Array[Byte]): Digest32 = Blake2b256(input) def hash192(input: Array[Byte]): Array[Byte] = hash256(input).take(24) -} +} \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index 65f5c87c09..4e3b4cd39d 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -21,7 +21,7 @@ object SigSerializer { def traverseNode(node: UncheckedSigmaTree, acc: Array[Byte], writeChallenge: Boolean = true): Array[Byte] = { - val parentChal = (if (writeChallenge) node.challenge else Array.emptyByteArray) + val parentChal = if (writeChallenge) node.challenge else Array.emptyByteArray node match { case dl: UncheckedSchnorr => acc ++ diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index 4e816470ec..c3e74e7191 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -6,6 +6,9 @@ import com.google.common.primitives.Shorts import gf2t.GF2_192_Poly import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, ProveDlog} import sigmastate.basics.VerifierMessage.Challenge +import sigmastate.Values.{SigmaBoolean, SigmaPropConstant} +import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, FirstProverMessage, ProveDHTuple, SigmaProtocol} +import sigmastate.serialization.ErgoTreeSerializer import sigmastate.Values.{ErgoTree, SigmaBoolean, SigmaPropConstant} import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, FirstProverMessage, ProveDHTuple} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer @@ -24,7 +27,7 @@ trait ProofTree extends Product trait ProofTreeLeaf extends ProofTree { val proposition: SigmaBoolean - val commitmentOpt: Option[FirstProverMessage[_]] + val commitmentOpt: Option[FirstProverMessage] } trait ProofTreeConjecture extends ProofTree { diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index d05f05a82e..5b1cea0cce 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -597,7 +597,7 @@ object Values { case FalseProp.opCode => FalseProp case TrueProp.opCode => TrueProp case ProveDlogCode => dlogSerializer.parse(r) - case ProveDHTupleCode => dhtSerializer.parse(r) + case ProveDiffieHellmanTupleCode => dhtSerializer.parse(r) case AndCode => val n = r.getUShort() val children = new Array[SigmaBoolean](n) @@ -954,6 +954,14 @@ object Values { _complexity } + /** Serialized proposition expression of SigmaProp type with + * ConstantPlaceholder nodes instead of Constant nodes + */ + lazy val template: Array[Byte] = { + val r = SigmaSerializer.startReader(bytes) + DefaultSerializer.deserializeHeaderWithTreeBytes(r)._4 + } + /** Get proposition expression from this contract. * When root.isRight then * if replaceConstants == false this is the same as `root.right.get`. diff --git a/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala b/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala index c248788491..a75447bc68 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/BcDlogGroup.scala @@ -166,13 +166,13 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex override lazy val identity: ElemType = curve.getInfinity.asInstanceOf[ElemType] - def createPoint(x: BigInteger, y: BigInteger): ElemType = curve.createPoint(x, y).asInstanceOf[ElemType] - - /** - * Creates ECPoint.Fp with infinity values + * Create point from its affine coordinates + * @param x - X coordinate + * @param y - Y coordinate + * @return */ - lazy val getInfinity: ElemType = curve.getInfinity.asInstanceOf[ElemType] + def createPoint(x: BigInteger, y: BigInteger): ElemType = curve.createPoint(x, y).asInstanceOf[ElemType] /** @@ -182,7 +182,7 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex * @return the inverse element of the given GroupElement * @throws IllegalArgumentException **/ - override def getInverse(groupElement: ElemType): ElemType = + override def inverseOf(groupElement: ElemType): ElemType = groupElement.negate().asInstanceOf[ElemType] /** @@ -413,54 +413,9 @@ abstract class BcDlogGroup[ElemType <: ECPoint](val x9params: X9ECParameters) ex } } -object SecP384R1 extends BcDlogGroup[SecP384R1Point](CustomNamedCurves.getByName("secp384r1")) with App { - val elems = 5000 - val base = generator - val exps = (1 to elems).map { _ => - val one = BigInteger.ONE - val qMinusOne = x9params.getN.subtract(one) - // choose a random number x in Zq* - BigIntegers.createRandomInRange(one, qMinusOne, secureRandom) - }.toArray - - println(exps.map(e => exponentiateWithPreComputedValues(base, e) == exponentiate(base, e)).forall(_ == true)) - - var t0 = System.currentTimeMillis() - exps.foreach(exp => exponentiate(base, exp)) - println(System.currentTimeMillis() - t0) +object SecP384R1 extends BcDlogGroup[SecP384R1Point](CustomNamedCurves.getByName("secp384r1")) - - t0 = System.currentTimeMillis() - exps.foreach(exp => exponentiateWithPreComputedValues(base, exp)) - println(System.currentTimeMillis() - t0) -} - -object SecP521R1 extends BcDlogGroup[SecP521R1Point](CustomNamedCurves.getByName("secp521r1")) with App { - val elems = 1000 - val bases = (1 to elems).map(_ => createRandomGenerator()).toArray - val exps = (1 to elems).map { _ => - val one = BigInteger.ONE - val qMinusOne = x9params.getN.subtract(one) - // choose a random number x in Zq* - BigIntegers.createRandomInRange(one, qMinusOne, secureRandom) - }.toArray - - var t0 = System.currentTimeMillis() - val naive = computeNaive(bases, exps) - println(System.currentTimeMillis() - t0) - - t0 = System.currentTimeMillis() - val ll = computeLL(bases, exps) - println(System.currentTimeMillis() - t0) - - println(naive.normalize().getAffineXCoord) - println(naive.normalize().getAffineYCoord) - - println(ll.normalize().getAffineXCoord) - println(ll.normalize().getAffineYCoord) - - println(naive == ll) -} +object SecP521R1 extends BcDlogGroup[SecP521R1Point](CustomNamedCurves.getByName("secp521r1")) object Curve25519 extends BcDlogGroup[Curve25519Point](CustomNamedCurves.getByName("curve25519")) diff --git a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala index 1474ca9c25..e20f2d2360 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -5,6 +5,7 @@ import java.math.BigInteger import org.bouncycastle.util.BigIntegers import sigmastate.Values._ import Value.PropositionCode +import scorex.util.encode.Base16 import sigmastate._ import sigmastate.eval._ import sigmastate.basics.VerifierMessage.Challenge @@ -23,7 +24,8 @@ object DLogProtocol { /** Construct a new SigmaBoolean value representing public key of discrete logarithm signature protocol. */ case class ProveDlog(value: EcPointType) - extends SigmaProofOfKnowledgeTree[DLogSigmaProtocol, DLogProverInput] { + extends SigmaProofOfKnowledgeLeaf[DLogSigmaProtocol, DLogProverInput] { + override val opCode: OpCode = OpCodes.ProveDlogCode lazy val h: EcPointType = value lazy val pkBytes: Array[Byte] = GroupElementSerializer.toBytes(h) @@ -64,14 +66,17 @@ object DLogProtocol { } } - case class FirstDLogProverMessage(ecData: EcPointType) extends FirstProverMessage[DLogSigmaProtocol] { + case class FirstDLogProverMessage(ecData: EcPointType) extends FirstProverMessage { + override type SP = DLogSigmaProtocol override def bytes: Array[Byte] = { GroupElementSerializer.toBytes(ecData) } + + override def toString: Idn = s"FirstDLogProverMessage(${Base16.encode(bytes)})" } - case class SecondDLogProverMessage(z: BigInt) extends SecondProverMessage[DLogSigmaProtocol] { - override def bytes: Array[Byte] = z.toByteArray + case class SecondDLogProverMessage(z: BigInt) extends SecondProverMessage { + override type SP = DLogSigmaProtocol } class DLogInteractiveProver(override val publicInput: ProveDlog, override val privateInputOpt: Option[DLogProverInput]) @@ -83,7 +88,7 @@ object DLogProtocol { assert(privateInputOpt.isDefined, "Secret is not known") assert(rOpt.isEmpty, "Already generated r") - val (r, fm) = DLogInteractiveProver.firstMessage(publicInput) + val (r, fm) = DLogInteractiveProver.firstMessage() rOpt = Some(r) fm } @@ -110,7 +115,7 @@ object DLogProtocol { object DLogInteractiveProver { import CryptoConstants.secureRandom - def firstMessage(publicInput: ProveDlog): (BigInteger, FirstDLogProverMessage) = { + def firstMessage(): (BigInteger, FirstDLogProverMessage) = { import CryptoConstants.dlogGroup val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) @@ -164,7 +169,7 @@ object DLogProtocol { dlogGroup.multiplyGroupElements( dlogGroup.exponentiate(g, secondMessage.z.underlying()), - dlogGroup.getInverse(dlogGroup.exponentiate(h, new BigInteger(1, challenge)))) + dlogGroup.inverseOf(dlogGroup.exponentiate(h, new BigInteger(1, challenge)))) } } diff --git a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index d2d23de5d6..db27256ddc 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -26,6 +26,7 @@ case class DiffieHellmanTupleProverInput(w: BigInteger, commonInput: ProveDHTupl } object DiffieHellmanTupleProverInput { + import sigmastate.interpreter.CryptoConstants.dlogGroup def random(): DiffieHellmanTupleProverInput = { @@ -43,24 +44,28 @@ object DiffieHellmanTupleProverInput { //a = g^r, b = h^r case class FirstDiffieHellmanTupleProverMessage(a: CryptoConstants.EcPointType, b: CryptoConstants.EcPointType) - extends FirstProverMessage[DiffieHellmanTupleProtocol] { + extends FirstProverMessage { + + override type SP = DiffieHellmanTupleProtocol + override def bytes: Array[Byte] = { GroupElementSerializer.toBytes(a) ++ GroupElementSerializer.toBytes(b) } } //z = r + ew mod q -case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) - extends SecondProverMessage[DiffieHellmanTupleProtocol] { - override def bytes: Array[PropositionCode] = ??? +case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) extends SecondProverMessage { + + override type SP = DiffieHellmanTupleProtocol + } /** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. - * Common input: (g,h,u,v)*/ + * Common input: (g,h,u,v) */ case class ProveDHTuple(gv: EcPointType, hv: EcPointType, uv: EcPointType, vv: EcPointType) extends SigmaProtocolCommonInput[DiffieHellmanTupleProtocol] - with SigmaProofOfKnowledgeTree[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { - override val opCode: OpCode = OpCodes.ProveDHTupleCode + with SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { + override val opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode lazy val g = gv lazy val h = hv lazy val u = uv @@ -115,6 +120,7 @@ class DiffieHellmanTupleInteractiveProver(override val publicInput: ProveDHTuple } object DiffieHellmanTupleInteractiveProver { + import sigmastate.interpreter.CryptoConstants.dlogGroup def firstMessage(publicInput: ProveDHTuple): (BigInteger, FirstDiffieHellmanTupleProverMessage) = { @@ -136,7 +142,7 @@ object DiffieHellmanTupleInteractiveProver { } def simulate(publicInput: ProveDHTuple, challenge: Challenge): - (FirstDiffieHellmanTupleProverMessage, SecondDiffieHellmanTupleProverMessage) = { + (FirstDiffieHellmanTupleProverMessage, SecondDiffieHellmanTupleProverMessage) = { val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) @@ -187,8 +193,8 @@ object DiffieHellmanTupleInteractiveProver { val uToE = dlogGroup.exponentiate(u, e) val vToE = dlogGroup.exponentiate(v, e) - val a = dlogGroup.multiplyGroupElements(gToZ, dlogGroup.getInverse(uToE)) - val b = dlogGroup.multiplyGroupElements(hToZ, dlogGroup.getInverse(vToE)) + val a = dlogGroup.multiplyGroupElements(gToZ, dlogGroup.inverseOf(uToE)) + val b = dlogGroup.multiplyGroupElements(hToZ, dlogGroup.inverseOf(vToE)) a -> b } } \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala b/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala index 9bfced419c..8df0f87e00 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DlogGroup.scala @@ -17,22 +17,6 @@ import org.bouncycastle.math.ec.ECPoint * In cryptography, we are interested in groups for which the discrete logarithm problem * (Dlog for short) is assumed to be hard. The most known groups of that kind are some Elliptic curve groups. * - * Another issue pertaining elliptic curves is the need to find a suitable mapping that will convert an arbitrary - * message (that is some binary string) to an element of the group and vice-versa. - * - * Only a subset of the messages can be effectively mapped to a group element in such a way that there is a one-to-one - * injection that converts the string to a group element and vice-versa. - * - * On the other hand, any group element can be mapped to some string. - * - * In this case, the operation is not invertible. This functionality is implemented by the functions: - * - {@code encodeByteArrayToGroupElement(binaryString: Array[Byte]): ElemType} - * - {@code decodeGroupElementToByteArray(element: ElemType) : Array[Byte]} - * - {@code mapAnyGroupElementToByteArray(element: ElemType): Array[Byte]} - * - * The first two work as a pair and decodeGroupElementToByteArray is the inverse of encodeByteArrayToGroupElement, - * whereas the last one works alone and does not have an inverse. - * * @tparam ElemType is concrete type */ trait DlogGroup[ElemType <: ECPoint] { @@ -73,7 +57,7 @@ trait DlogGroup[ElemType <: ECPoint] { * @return the inverse element of the given GroupElement * @throws IllegalArgumentException **/ - def getInverse(groupElement: ElemType): ElemType + def inverseOf(groupElement: ElemType): ElemType /** * Raises the base GroupElement to the exponent. The result is another GroupElement. diff --git a/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala b/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala index b5b02b711b..d1acb7a9cd 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala @@ -4,7 +4,7 @@ import java.security.SecureRandom import sigmastate.basics.VerifierMessage.Challenge import sigmastate.interpreter.CryptoConstants -import sigmastate.{SigmaProofOfKnowledgeTree, UncheckedTree} +import sigmastate.{SigmaProofOfKnowledgeLeaf, UncheckedTree} import supertagged.TaggedType import scala.concurrent.Future @@ -23,7 +23,6 @@ import scala.concurrent.Future trait TranscriptMessage { - def bytes: Array[Byte] } /** The message sent by a prover to its associated verifier as part of a sigma protocol interaction. */ @@ -39,17 +38,23 @@ object VerifierMessage { } /** First message from the prover (message `a` of `SigmaProtocol`)*/ -trait FirstProverMessage[SP <: SigmaProtocol[SP]] extends ProverMessage +trait FirstProverMessage extends ProverMessage { + type SP <: SigmaProtocol[SP] + + def bytes: Array[Byte] +} /** Second message from the prover (message `z` of `SigmaProtocol`)*/ -trait SecondProverMessage[SP <: SigmaProtocol[SP]] extends ProverMessage +trait SecondProverMessage extends ProverMessage { + type SP <: SigmaProtocol[SP] +} /** Abstract template for sigma protocols. * For details see the following book * [1] Efficient Secure Two-Party Protocols - Techniques and Constructions, p.150)*/ trait SigmaProtocol[SP <: SigmaProtocol[SP]] { - type A <: FirstProverMessage[SP] - type Z <: SecondProverMessage[SP] + type A <: FirstProverMessage + type Z <: SecondProverMessage } @@ -96,7 +101,7 @@ trait ZeroKnowledgeProofOfKnowledge[SP <: SigmaProtocol[SP]] trait NonInteractiveProver[SP <: SigmaProtocol[SP], PI <: SigmaProtocolPrivateInput[SP, CI], - CI <: SigmaProofOfKnowledgeTree[SP, PI], + CI <: SigmaProofOfKnowledgeLeaf[SP, PI], P <: UncheckedTree] extends Prover[SP, CI, PI] { diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index 6f50a7a729..9c5561a483 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -4,24 +4,23 @@ import java.math.BigInteger import scalan.{ExactNumeric, ExactIntegral, ExactOrderingImpl} -import scala.math.{LowPriorityOrderingImplicits, Integral, Ordering} +import scala.math.{Integral, Ordering} import special.sigma._ -import scalan.util.Extensions._ import sigmastate.eval.Extensions._ -import sigmastate.eval.NumericOps.BigIntIsExactNumeric.n +import special.collection.Coll -object OrderingOps extends LowPriorityOrderingImplicits { +object OrderingOps { def apply[T](implicit ord: Ordering[T]) = ord trait BigIntegerOrdering extends Ordering[BigInteger] { def compare(x: BigInteger, y: BigInteger) = x.compareTo(y) } - implicit object BigInteger extends BigIntegerOrdering + implicit object BigIntegerOrdering extends BigIntegerOrdering trait BigIntOrdering extends Ordering[BigInt] { def compare(x: BigInt, y: BigInt) = x.compareTo(y) } - implicit object BigInt extends BigIntOrdering + implicit object BigIntOrdering extends BigIntOrdering } object NumericOps { diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 81ce7fb44a..5d979ca418 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -4,40 +4,49 @@ import java.math.BigInteger import java.util import org.bouncycastle.math.ec.ECPoint +import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.{ErgoBox, SigmaConstants} import org.ergoplatform.validation.ValidationRules import scorex.crypto.authds.avltree.batch._ -import scorex.crypto.authds.{ADDigest, ADKey, ADValue, SerializedAdProof} +import scorex.crypto.authds.{ADDigest, ADKey, SerializedAdProof, ADValue} import sigmastate.SCollection.SByteArray import sigmastate.{TrivialProp, _} -import sigmastate.Values.{Constant, ConstantNode, ErgoTree, EvaluatedValue, SValue, SigmaBoolean, Value} +import sigmastate.Values.{Constant, EvaluatedValue, SValue, ConstantNode, Value, ErgoTree, SigmaBoolean} import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.interpreter.{CryptoConstants, Interpreter} -import special.collection.{CCostedBuilder, CSizeOption, Coll, CollType, CostedBuilder, Size, SizeColl, SizeOption} -import special.sigma.{Box, _} +import special.collection.{Size, CSizeOption, SizeColl, CCostedBuilder, CollType, SizeOption, CostedBuilder, Coll} +import special.sigma._ import sigmastate.eval.Extensions._ import spire.syntax.all.cfor -import scala.util.{Failure, Success} +import scala.util.{Success, Failure} import scalan.RType -import scorex.crypto.hash.{Blake2b256, Digest32, Sha256} +import scorex.crypto.hash.{Digest32, Sha256, Blake2b256} import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.lang.Terms.OperationId import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer} +import sigmastate.serialization.{SigmaSerializer, GroupElementSerializer} import special.Types.TupleType import scala.reflect.ClassTag +/** Interface implmented by wrappers to provide access to the underlying wrapped value. */ trait WrapperOf[T] { + /** The data value wrapped by this wrapper. */ def wrappedValue: T } +/** A default implementation of [[BigInt]] interface. + * @see [[BigInt]] for detailed descriptions + */ case class CBigInt(override val wrappedValue: BigInteger) extends TestBigInt(wrappedValue) with WrapperOf[BigInteger] { override val dsl = CostingSigmaDslBuilder } +/** A default implementation of [[GroupElement]] interface. + * @see [[GroupElement]] for detailed descriptions + */ case class CGroupElement(override val wrappedValue: EcPointType) extends TestGroupElement(wrappedValue) with WrapperOf[ECPoint] { override val dsl = CostingSigmaDslBuilder @@ -45,9 +54,13 @@ case class CGroupElement(override val wrappedValue: EcPointType) extends TestGro } +/** A default implementation of [[SigmaProp]] interface. + * @see [[SigmaProp]] for detailed descriptions + */ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[SigmaBoolean] { override def wrappedValue: SigmaBoolean = sigmaTree + // TODO refactor: remove this (it shouldn't be used in interpreter) override def isValid: Boolean = sigmaTree match { case p: TrivialProp => p.condition case _ => sys.error(s"Method CostingSigmaProp.isValid is not defined for $sigmaTree") @@ -66,6 +79,7 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ CSigmaProp(CAND.normalized(Array(sigmaTree, other.sigmaTree))) } + // TODO refactor: remove this (it shouldn't be used in interpreter) override def &&(other: Boolean): SigmaProp = CSigmaProp(CAND.normalized(Array(sigmaTree, TrivialProp(other)))) @@ -74,23 +88,25 @@ case class CSigmaProp(sigmaTree: SigmaBoolean) extends SigmaProp with WrapperOf[ CSigmaProp(COR.normalized(Array(sigmaTree, other.sigmaTree))) } + // TODO refactor: remove this (it shouldn't be used in interpreter) override def ||(other: Boolean): SigmaProp = CSigmaProp(COR.normalized(Array(sigmaTree, TrivialProp(other)))) override def toString: String = s"SigmaProp(${wrappedValue.showToString})" } +/** A default implementation of [[AvlTree]] interface. + * @see [[AvlTree]] for detailed descriptions + */ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTreeData] { val builder = CostingSigmaDslBuilder val Colls = builder.Colls override def wrappedValue: AvlTreeData = treeData - def startingDigest: Coll[Byte] = Colls.fromArray(treeData.digest) + override def keyLength: Int = treeData.keyLength - def keyLength: Int = treeData.keyLength - - def enabledOperations = treeData.treeFlags.serializeToByte + override def enabledOperations = treeData.treeFlags.serializeToByte override def isInsertAllowed: Boolean = treeData.treeFlags.insertAllowed @@ -103,15 +119,11 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre this.copy(treeData = td) } - def valueLengthOpt: Option[Int] = treeData.valueLengthOpt - - def cost: Int = 1 - - def dataSize: Long = SAvlTree.dataSize(treeData.asInstanceOf[SType#WrappedType]) + override def valueLengthOpt: Option[Int] = treeData.valueLengthOpt override def digest: Coll[Byte] = Colls.fromArray(treeData.digest) - def updateDigest(newDigest: Coll[Byte]): AvlTree = { + override def updateDigest(newDigest: Coll[Byte]): AvlTree = { val td = treeData.copy(digest = ADDigest @@ newDigest.toArray) this.copy(treeData = td) } @@ -120,7 +132,10 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre val adProof = SerializedAdProof @@ proof.toArray val bv = new BatchAVLVerifier[Digest32, Blake2b256.type]( treeData.digest, adProof, - treeData.keyLength, treeData.valueLengthOpt) + treeData.keyLength, treeData.valueLengthOpt) { + /** Override default logging which outputs stack trace to the console. */ + override protected def logError(t: Throwable): Unit = {} + } bv } @@ -128,11 +143,11 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre val keyBytes = key.toArray val bv = createVerifier(proof) bv.performOneOperation(Lookup(ADKey @@ keyBytes)) match { - case Failure(_) => false case Success(r) => r match { case Some(_) => true case _ => false } + case Failure(_) => false } } @@ -140,11 +155,11 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre val keyBytes = key.toArray val bv = createVerifier(proof) bv.performOneOperation(Lookup(ADKey @@ keyBytes)) match { - case Failure(_) => Interpreter.error(s"Tree proof is incorrect $treeData") case Success(r) => r match { case Some(v) => Some(Colls.fromArray(v)) case _ => None } + case Failure(_) => Interpreter.error(s"Tree proof is incorrect $treeData") } } @@ -152,21 +167,21 @@ case class CAvlTree(treeData: AvlTreeData) extends AvlTree with WrapperOf[AvlTre val bv = createVerifier(proof) keys.map { key => bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match { - case Failure(_) => Interpreter.error(s"Tree proof is incorrect $treeData") case Success(r) => r match { case Some(v) => Some(Colls.fromArray(v)) case _ => None } + case Failure(_) => Interpreter.error(s"Tree proof is incorrect $treeData") } } } - override def insert(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] = { + override def insert(entries: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree] = { if (!isInsertAllowed) { None } else { val bv = createVerifier(proof) - operations.forall { case (key, value) => + entries.forall { case (key, value) => val insertRes = bv.performOneOperation(Insert(ADKey @@ key.toArray, ADValue @@ value.toArray)) if (insertRes.isFailure) { Interpreter.error(s"Incorrect insert for $treeData (key: $key, value: $value, digest: $digest): ${insertRes.failed.get}}") @@ -246,6 +261,9 @@ class EvalSizeBuilder extends CSizeBuilder { } } +/** A default implementation of [[Box]] interface. + * @see [[Box]] for detailed descriptions + */ case class CostingBox(isCost: Boolean, val ebox: ErgoBox) extends Box with WrapperOf[ErgoBox] { val builder = CostingSigmaDslBuilder @@ -401,6 +419,9 @@ object CFunc { val maxCost = 1000 } +/** A default implementation of [[PreHeader]] interface. + * @see [[PreHeader]] for detailed descriptions + */ case class CPreHeader( version: Byte, parentId: Coll[Byte], @@ -411,6 +432,9 @@ case class CPreHeader( votes: Coll[Byte] ) extends PreHeader {} +/** A default implementation of [[Header]] interface. + * @see [[Header]] for detailed descriptions + */ case class CHeader( id: Coll[Byte], version: Byte, @@ -435,6 +459,9 @@ object CHeader { val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value } +/** A default implementation of [[CostModel]] interface. + * @see [[CostModel]] for detailed descriptions + */ class CCostModel extends CostModel { private def costOf(opName: String, opType: SFunc): Int = { val operId = OperationId(opName, opType) @@ -467,6 +494,10 @@ class CCostModel extends CostModel { def PubKeySize: Long = CryptoConstants.EncodedGroupElementLength } + +/** A default implementation of [[SigmaDslBuilder]] interface. + * @see [[SigmaDslBuilder]] for detailed descriptions + */ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => implicit val validationSettings = ValidationRules.currentSettings @@ -591,6 +622,13 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => this.GroupElement(CryptoConstants.dlogGroup.generator) } + /** + * @return the identity of the Dlog group used in ErgoTree + */ + def groupIdentity: GroupElement = { + this.GroupElement(CryptoConstants.dlogGroup.identity) + } + override def substConstants[T](scriptBytes: Coll[Byte], positions: Coll[Int], newValues: Coll[T]) @@ -605,10 +643,15 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => val p = GroupElementSerializer.parse(r) this.GroupElement(p) } + } +/** Default singleton instance of Global object, which implements global ErgoTree functions. */ object CostingSigmaDslBuilder extends CostingSigmaDslBuilder +/** A default implementation of [[Context]] interface. + * @see [[Context]] for detailed descriptions + */ case class CostingDataContext( _dataInputs: Coll[Box], override val headers: Coll[Header], diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index bac1ef19fe..a063fe9394 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -295,7 +295,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case SDBM.blake2b256(_, _) => CalcBlake2b256Code case SDBM.sha256(_, _) => CalcSha256Code case SDBM.proveDlog(_, _) => ProveDlogCode - case SDBM.proveDHTuple(_, _, _, _, _) => ProveDHTupleCode + case SDBM.proveDHTuple(_, _, _, _, _) => ProveDiffieHellmanTupleCode case SDBM.sigmaProp(_, _) => BoolToSigmaPropCode case SDBM.decodePoint(_, _) => DecodePointCode case SDBM.xorOf(_, _) => XorOfCode diff --git a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala index 627312cd3c..451fc9297c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Extensions.scala @@ -24,6 +24,11 @@ object Extensions { @inline def toBigInt: BigInt = CostingSigmaDslBuilder.BigInt(BigInteger.valueOf(x.toLong)) } + implicit class LongExt(val x: Long) extends AnyVal { + /** Convert this value to BigInt. */ + @inline def toBigInt: BigInt = CostingSigmaDslBuilder.BigInt(BigInteger.valueOf(x)) + } + implicit class ArrayOps[T: RType](arr: Array[T]) { @inline def toColl: Coll[T] = Colls.fromArray(arr) } diff --git a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala index 8e661be19b..52b43b0088 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/TreeBuilding.scala @@ -380,6 +380,9 @@ trait TreeBuilding extends RuntimeCosting { IR: IRContext => case GM.exp(In(obj), In(arg)) => mkExponentiate(obj.asGroupElement, arg.asBigInt) + case GM.multiply(In(obj), In(arg)) => + mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement) + // Fallback MethodCall rule: should be the last in this list of cases case Def(MethodCall(objSym, m, argSyms, _)) => val obj = recurse[SType](objSym) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Hint.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Hint.scala new file mode 100644 index 0000000000..1998bfd15d --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Hint.scala @@ -0,0 +1,125 @@ +package sigmastate.interpreter + +import java.math.BigInteger + +import sigmastate.UncheckedTree +import sigmastate.Values.SigmaBoolean +import sigmastate.basics.FirstProverMessage +import sigmastate.basics.VerifierMessage.Challenge + +/** + * A hint for a prover which helps the prover to prove a statement. For example, if the statement is "pk1 && pk2", + * and the prover knows only a secret for the public key pk1, the prover fails on proving without a hint. But if the + * prover knows that pk2 is known to another party, the prover may prove the statement (with an empty proof for "pk2"). + */ +trait Hint + +/** + * A hint which is indicating that a secret associated with its public image "image" is already proven. + */ +abstract class SecretProven extends Hint { + + /** + * Public image of a secret which is proven + */ + def image: SigmaBoolean + + /** + * Challenge used for a proof + */ + def challenge: Challenge + + /** + * Proof in a tree form + */ + def uncheckedTree: UncheckedTree +} + +/** + * A hint which contains a proof-of-knowledge for a secret associated with its public image "image", + * with also the mark that the proof is real. + */ +case class RealSecretProof(image: SigmaBoolean, + challenge: Challenge, + uncheckedTree: UncheckedTree) extends SecretProven + +/** + * A hint which contains a proof-of-knowledge for a secret associated with its public image "image", + * with also the mark that the proof is real. + */ +case class SimulatedSecretProof(image: SigmaBoolean, + challenge: Challenge, + uncheckedTree: UncheckedTree) extends SecretProven + + +/** + * A family of hints which are about a correspondence between a public image of a secret image and prover's commitment + * to randomness ("a" in a sigma protocol). + */ +abstract class CommitmentHint extends Hint { + def image: SigmaBoolean + def commitment: FirstProverMessage +} + +/** + * A hint which a commitment to randomness associated with a public image of a secret, as well as randomness itself. + * Please note that this randomness should be kept in secret by the prover. + * + * @param image - image of a secret + * @param secretRandomness - randomness + * @param commitment - commitment to randomness used while proving knowledge of the secret + */ +case class OwnCommitment(override val image: SigmaBoolean, + secretRandomness: BigInteger, + commitment: FirstProverMessage) extends CommitmentHint + +/** + * A hint which contains a commitment to randomness associated with a public image of a secret. + * + * @param image - image of a secret + * @param commitment - commitment to randomness used while proving knowledge of the secret + */ +case class RealCommitment(override val image: SigmaBoolean, commitment: FirstProverMessage) extends CommitmentHint + +/** + * A hint which contains a commitment to randomness associated with a public image of a secret. + * + * @param image - image of a secret + * @param commitment - commitment to randomness used while proving knowledge of the secret + */ +case class SimulatedCommitment(override val image: SigmaBoolean, commitment: FirstProverMessage) extends CommitmentHint + + +/** + * Collection of hints to be used by a prover + * + * @param hints - hints stored in the bag + */ +case class HintsBag(hints: Seq[Hint]) { + + lazy val realProofs: Seq[RealSecretProof] = hints.collect { case osp: RealSecretProof => osp } + lazy val simulatedProofs: Seq[SimulatedSecretProof] = hints.collect { case osp: SimulatedSecretProof => osp } + + lazy val proofs: Seq[SecretProven] = realProofs ++ simulatedProofs + + lazy val commitments: Seq[CommitmentHint] = hints.collect { case ch: CommitmentHint => ch } + lazy val realCommitments: Seq[RealCommitment] = hints.collect { case rc: RealCommitment => rc } + lazy val ownCommitments: Seq[OwnCommitment] = hints.collect { case oc: OwnCommitment => oc } + + lazy val realImages: Seq[SigmaBoolean] = realProofs.map(_.image) ++ realCommitments.map(_.image) + + def addHint(hint: Hint): HintsBag = HintsBag(hint +: hints) + + def addHints(newHints: Hint*): HintsBag = HintsBag(newHints ++ hints) + + def ++(other: HintsBag): HintsBag = HintsBag(other.hints ++ hints) + + override def toString: String = s"HintsBag(${hints.mkString("\n")})" + +} + +object HintsBag { + + val empty = HintsBag(Seq.empty) + +} diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index f867715b13..1ddbce9ad5 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -3,18 +3,19 @@ package sigmastate.interpreter import java.util import java.lang.{Math => JMath} -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, rule, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, rule, strategy} import org.bitbucket.inkytonik.kiama.rewriting.Strategy -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, DLogInteractiveProver} +import org.ergoplatform.validation.SigmaValidationSettings +import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMessage} import scorex.util.ScorexLogging import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate.eval.{IRContext, Sized} import sigmastate.lang.Terms.ValueOps import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv} -import sigmastate.lang.exceptions.{InterpreterException, CostLimitException} -import sigmastate.serialization.{ValueSerializer, SigmaSerializer} +import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} +import sigmastate.lang.exceptions.{CostLimitException, InterpreterException} +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} import org.ergoplatform.validation.ValidationRules._ @@ -85,6 +86,22 @@ trait Interpreter extends ScorexLogging { class MutableCell[T](var value: T) + /** Extracts proposition for ErgoTree handing soft-fork condition. + * @note soft-fork handler */ + def propositionFromErgoTree(ergoTree: ErgoTree, context: CTX): SigmaPropValue = { + val validationSettings = context.validationSettings + val prop = ergoTree.root match { + case Right(_) => + ergoTree.toProposition(ergoTree.isConstantSegregation) + case Left(UnparsedErgoTree(_, error)) if validationSettings.isSoftFork(error) => + TrueSigmaProp + case Left(UnparsedErgoTree(_, error)) => + throw new InterpreterException( + "Script has not been recognized due to ValidationException, and it cannot be accepted as soft-fork.", None, Some(error)) + } + prop + } + /** Substitute Deserialize* nodes with deserialized subtrees * We can estimate cost of the tree evaluation only after this step.*/ def applyDeserializeContext(context: CTX, exp: Value[SType]): (BoolValue, CTX) = { @@ -99,7 +116,7 @@ trait Interpreter extends ScorexLogging { def checkCost(context: CTX, exp: Value[SType], costF: Ref[((Int, IR.Size[IR.Context])) => Int]): Int = { import IR.Size._ - import IR.Context._; + import IR.Context._ val costingCtx = context.toSigmaContext(IR, isCost = true) val maxCost = context.costLimit val costFun = IR.compile[(Int, SSize[SContext]), Int, (Int, Size[Context]), Int](IR.getDataEnv, costF, Some(maxCost)) @@ -127,7 +144,7 @@ trait Interpreter extends ScorexLogging { res } - /** This method is used in both prover and verifier to compute SigmaProp value. + /** This method is used in both prover and verifier to compute SigmaBoolean value. * As the first step the cost of computing the `exp` expression in the given context is estimated. * If cost is above limit * then exception is returned and `exp` is not executed @@ -168,19 +185,34 @@ trait Interpreter extends ScorexLogging { def reduceToCrypto(context: CTX, exp: Value[SType]): Try[ReductionResult] = reduceToCrypto(context, Interpreter.emptyEnv, exp) - /** Extracts proposition for ErgoTree handing soft-fork condition. - * @note soft-fork handler */ - def propositionFromErgoTree(tree: ErgoTree, ctx: CTX): SigmaPropValue = { - val prop = tree.root match { - case Right(_) => - tree.toProposition(tree.isConstantSegregation) - case Left(UnparsedErgoTree(_, error)) if ctx.validationSettings.isSoftFork(error) => - TrueSigmaProp - case Left(UnparsedErgoTree(_, error)) => - throw new InterpreterException( - "Script has not been recognized due to ValidationException, and it cannot be accepted as soft-fork.", None, Some(error)) + + /** + * Full reduction of initial expression given in the ErgoTree form to a SigmaBoolean value + * (which encodes whether a sigma-protocol proposition or a boolean value, so true or false). + * + * Works as follows: + * 1) parse ErgoTree instance into a typed AST + * 2) go bottom-up the tree to replace DeserializeContext nodes only + * 3) estimate cost and reduce the AST to a SigmaBoolean instance (so sigma-tree or trivial boolean value) + * + * + * @param ergoTree - input ErgoTree expression to reduce + * @param context - context used in reduction + * @param env - script environment + * @return sigma boolean and the updated cost counter after reduction + */ + def fullReduction(ergoTree: ErgoTree, + context: CTX, + env: ScriptEnv): (SigmaBoolean, Long) = { + implicit val vs: SigmaValidationSettings = context.validationSettings + val prop = propositionFromErgoTree(ergoTree, context) + val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { + applyDeserializeContext(context, prop) } - prop + + // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds + // and the rest of the verification is also trivial + reduceToCrypto(context2, env, propTree).getOrThrow } /** Executes the script in a given context. @@ -189,41 +221,34 @@ trait Interpreter extends ScorexLogging { * Step 3: Verify that the proof is presented to satisfy SigmaProp conditions. * * @param env environment of system variables used by the interpreter internally - * @param tree ErgoTree to execute in the given context and verify its result + * @param ergoTree ErgoTree expression to execute in the given context and verify its result * @param context the context in which `exp` should be executed * @param proof The proof of knowledge of the secrets which is expected by the resulting SigmaProp * @param message message bytes, which are used in verification of the proof * * @return verification result or Exception. - * If if the estimated cost of execution of the `tree` exceeds the limit (given in `context`), + * If if the estimated cost of execution of the `exp` exceeds the limit (given in `context`), * then exception if thrown and packed in Try. * If left component is false, then: * 1) script executed to false or - * 2) the given proof faild to validate resulting SigmaProp conditions. + * 2) the given proof failed to validate resulting SigmaProp conditions. * @see `reduceToCrypto` */ - def verify(env: ScriptEnv, tree: ErgoTree, + def verify(env: ScriptEnv, + ergoTree: ErgoTree, context: CTX, proof: Array[Byte], message: Array[Byte]): Try[VerificationResult] = { val (res, t) = BenchmarkUtil.measureTime(Try { - val initCost = JMath.addExact(tree.complexity.toLong, context.initCost) + val initCost = JMath.addExact(ergoTree.complexity.toLong, context.initCost) val remainingLimit = context.costLimit - initCost if (remainingLimit <= 0) throw new CostLimitException(initCost, msgCostLimitError(initCost, context.costLimit), None) - val context1 = context.withInitCost(initCost).asInstanceOf[CTX] - val prop = propositionFromErgoTree(tree, context1) - - implicit val vs = context1.validationSettings - val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context1)) { - applyDeserializeContext(context1, prop) - } + val contextWithCost = context.withInitCost(initCost).asInstanceOf[CTX] - // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds - // and the rest of the verification is also trivial - val (cProp, cost) = reduceToCrypto(context2, env, propTree).getOrThrow + val (cProp, cost) = fullReduction(ergoTree, contextWithCost, env) val checkingResult = cProp match { case TrivialProp.TrueProp => true @@ -285,29 +310,29 @@ trait Interpreter extends ScorexLogging { case _ => ??? }) - def verify(exp: ErgoTree, + def verify(ergoTree: ErgoTree, context: CTX, proverResult: ProverResult, message: Array[Byte]): Try[VerificationResult] = { val ctxv = context.withExtension(proverResult.extension).asInstanceOf[CTX] - verify(Interpreter.emptyEnv, exp, ctxv, proverResult.proof, message) + verify(Interpreter.emptyEnv, ergoTree, ctxv, proverResult.proof, message) } def verify(env: ScriptEnv, - exp: ErgoTree, + ergoTree: ErgoTree, context: CTX, proverResult: ProverResult, message: Array[Byte]): Try[VerificationResult] = { val ctxv = context.withExtension(proverResult.extension).asInstanceOf[CTX] - verify(env, exp, ctxv, proverResult.proof, message) + verify(env, ergoTree, ctxv, proverResult.proof, message) } - def verify(exp: ErgoTree, + def verify(ergoTree: ErgoTree, context: CTX, proof: ProofT, message: Array[Byte]): Try[VerificationResult] = { - verify(Interpreter.emptyEnv, exp, context, SigSerializer.toBytes(proof), message) + verify(Interpreter.emptyEnv, ergoTree, context, SigSerializer.toBytes(proof), message) } } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index 81b642ceaf..95e620076e 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -1,76 +1,30 @@ package sigmastate.interpreter -import java.util +import java.math.BigInteger import gf2t.{GF2_192, GF2_192_Poly} import org.bitbucket.inkytonik.kiama.attribution.AttributionCore import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, everywheretd, rule} import org.bitbucket.inkytonik.kiama.rewriting.Strategy -import org.ergoplatform.settings.ErgoAlgos import scalan.util.CollectionUtil._ import sigmastate.Values._ import sigmastate._ import sigmastate.basics.DLogProtocol._ import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{DiffieHellmanTupleInteractiveProver, DiffieHellmanTupleProverInput, ProveDHTuple, SigmaProtocolPrivateInput} +import sigmastate.basics.{DiffieHellmanTupleInteractiveProver, DiffieHellmanTupleProverInput, FirstDiffieHellmanTupleProverMessage, ProveDHTuple, SecondDiffieHellmanTupleProverMessage, SigmaProtocolPrivateInput} import sigmastate.lang.exceptions.CostLimitException -import sigmastate.serialization.SigmaSerializer -import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} -import Helpers._ -import scala.util.Try - -/** - * Proof of correctness of tx spending - * - * @param proof - proof that satisfies final sigma proposition - * @param extension - user-defined variables to be put into context - */ -class ProverResult(val proof: Array[Byte], val extension: ContextExtension) { - override def hashCode(): Int = util.Arrays.hashCode(proof) * 31 + extension.hashCode() - - override def equals(obj: scala.Any): Boolean = obj match { - case obj: ProverResult => - util.Arrays.equals(proof, obj.proof) && extension == obj.extension - case _ => false - } +import sigmastate.utils.Helpers - override def toString: Idn = s"ProverResult(${ErgoAlgos.encode(proof)},$extension)" -} - -object ProverResult { - val empty: ProverResult = ProverResult(Array[Byte](), ContextExtension.empty) - - def apply(proof: Array[Byte], extension: ContextExtension): ProverResult = - new ProverResult(proof, extension) - - object serializer extends SigmaSerializer[ProverResult, ProverResult] { - - override def serialize(obj: ProverResult, w: SigmaByteWriter): Unit = { - w.putUShort(obj.proof.length) - w.putBytes(obj.proof) - ContextExtension.serializer.serialize(obj.extension, w) - } - - override def parse(r: SigmaByteReader): ProverResult = { - val sigBytesCount = r.getUShort() - val proofBytes = r.getBytes(sigBytesCount) - val ce = ContextExtension.serializer.parse(r) - ProverResult(proofBytes, ce) - } - } -} +import scala.util.Try -case class CostedProverResult(override val proof: Array[Byte], - override val extension: ContextExtension, - cost: Long) extends ProverResult(proof, extension) /** * Interpreter with enhanced functionality to prove statements. */ -trait ProverInterpreter extends Interpreter with AttributionCore { +trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCore { - import CryptoConstants.secureRandomBytes import Interpreter._ + import CryptoConstants.secureRandomBytes override type ProofT = UncheckedTree @@ -78,7 +32,8 @@ trait ProverInterpreter extends Interpreter with AttributionCore { /** * The comments in this section are taken from the algorithm for the - * Sigma-protocol prover as described in the white paper + * Sigma-protocol prover as described in the ErgoScript white-paper + * https://ergoplatform.org/docs/ErgoScript.pdf , Appendix A * */ // todo: if we are concerned about timing attacks against the prover, we should make sure that this code @@ -87,10 +42,10 @@ trait ProverInterpreter extends Interpreter with AttributionCore { // todo: once the right value is (or is not) found. We should also make all loops look similar, the same // todo: amount of copying is done regardless of what's real or simulated, // todo: real vs. simulated computations take the same time, etc. - protected def prove(unprovenTree: UnprovenTree, message: Array[Byte]): ProofT = { + protected def prove(unprovenTree: UnprovenTree, message: Array[Byte], hintsBag: HintsBag): ProofT = { // Prover Step 1: Mark as real everything the prover can prove - val step1 = markReal(unprovenTree).get.asInstanceOf[UnprovenTree] + val step1 = markReal(hintsBag)(unprovenTree).get.asInstanceOf[UnprovenTree] // Prover Step 2: If the root of the tree is marked "simulated" then the prover does not have enough witnesses // to perform the proof. Abort. @@ -102,52 +57,62 @@ trait ProverInterpreter extends Interpreter with AttributionCore { // Prover Steps 4, 5, and 6 together: find challenges for simulated nodes; simulate simulated leaves; // compute commitments for real leaves - val step6 = simulateAndCommit(step3).get.asInstanceOf[UnprovenTree] + val step6 = simulateAndCommit(hintsBag)(step3).get.asInstanceOf[UnprovenTree] // Prover Steps 7: convert the relevant information in the tree (namely, tree structure, node types, - // the statements being proven and commitments at the leaves) - // to a string - val s = FiatShamirTree.toBytes(step6) + // the statements being proven and commitments at the leaves) to a bitstring. + // This bitstring corresponding to a proposition to prove is needed for Strong Fiat-Shamir transformation. + // See [BPW12] paper on Strong vs Weak Fiat-Shamir, + // (https://link.springer.com/content/pdf/10.1007/978-3-642-34961-4_38.pdf) + val propBytes = FiatShamirTree.toBytes(step6) - // Prover Step 8: compute the challenge for the root of the tree as the Fiat-Shamir hash of s + // Prover Step 8: compute the challenge for the root of the tree as the Fiat-Shamir hash of propBytes // and the message being signed. - val rootChallenge = Challenge @@ CryptoFunctions.hashFn(s ++ message) + val rootChallenge = Challenge @@ CryptoFunctions.hashFn(propBytes ++ message) val step8 = step6.withChallenge(rootChallenge) // Prover Step 9: complete the proof by computing challenges at real nodes and additionally responses at real leaves - val step9 = proving(step8).get.asInstanceOf[ProofTree] + val step9 = proving(hintsBag)(step8).get.asInstanceOf[ProofTree] // Syntactic step that performs a type conversion only convertToUnchecked(step9) } - def prove(exp: ErgoTree, context: CTX, message: Array[Byte]): Try[CostedProverResult] = - prove(emptyEnv, exp, context, message) + def prove(ergoTree: ErgoTree, + context: CTX, + message: Array[Byte], + hintsBag: HintsBag): Try[CostedProverResult] = + prove(emptyEnv, ergoTree, context, message, hintsBag) + + def prove(ergoTree: ErgoTree, + context: CTX, + message: Array[Byte]): Try[CostedProverResult] = + prove(emptyEnv, ergoTree, context, message, HintsBag.empty) + - def prove(env: ScriptEnv, tree: ErgoTree, ctx: CTX, message: Array[Byte]): Try[CostedProverResult] = Try { + def prove(env: ScriptEnv, + ergoTree: ErgoTree, + context: CTX, + message: Array[Byte], + hintsBag: HintsBag = HintsBag.empty): Try[CostedProverResult] = Try { import TrivialProp._ - val initCost = tree.complexity + ctx.initCost - val remainingLimit = ctx.costLimit - initCost + val initCost = ergoTree.complexity + context.initCost + val remainingLimit = context.costLimit - initCost if (remainingLimit <= 0) throw new CostLimitException(initCost, - s"Estimated execution cost $initCost exceeds the limit ${ctx.costLimit}", None) + s"Estimated execution cost $initCost exceeds the limit ${context.costLimit}", None) - val ctxUpdInitCost = ctx.withInitCost(initCost).asInstanceOf[CTX] + val ctxUpdInitCost = context.withInitCost(initCost).asInstanceOf[CTX] - val prop = propositionFromErgoTree(tree, ctxUpdInitCost) - val (propTree, _) = applyDeserializeContext(ctxUpdInitCost, prop) - val tried = reduceToCrypto(ctxUpdInitCost, env, propTree) - val (reducedProp, cost) = tried.getOrThrow - - def errorReducedToFalse = error("Script reduced to false") + val (reducedProp, cost) = fullReduction(ergoTree, ctxUpdInitCost, env) val proofTree = reducedProp match { case TrueProp => NoProof - case FalseProp => errorReducedToFalse + case FalseProp => error("Script reduced to false") case sigmaTree => val unprovenTree = convertToUnproven(sigmaTree) - prove(unprovenTree, message) + prove(unprovenTree, message, hintsBag) } // Prover Step 10: output the right information into the proof val proof = SigSerializer.toBytes(proofTree) @@ -160,8 +125,9 @@ trait ProverInterpreter extends Interpreter with AttributionCore { * necessary number of witnesses (for example, more than one child of an OR). * This will be corrected in the next step. * In a bottom-up traversal of the tree, do the following for each node: + * */ - val markReal: Strategy = everywherebu(rule[UnprovenTree] { + def markReal(hintsBag: HintsBag): Strategy = everywherebu(rule[UnprovenTree] { case and: CAndUnproven => // If the node is AND, mark it "real" if all of its children are marked real; else mark it "simulated" val simulated = and.children.exists(_.asInstanceOf[UnprovenTree].simulated) @@ -171,25 +137,21 @@ trait ProverInterpreter extends Interpreter with AttributionCore { val simulated = or.children.forall(_.asInstanceOf[UnprovenTree].simulated) or.copy(simulated = simulated) case t: CThresholdUnproven => - // If the node is TRESHOLD(k), mark it "real" if at least k of its children are marked real; else mark it "simulated" - val c = t.children.foldLeft(0) { (count, child) => + // If the node is THRESHOLD(k), mark it "real" if at least k of its children are marked real; else mark it "simulated" + val realCount = t.children.foldLeft(0) { (count, child) => count + (if (child.asInstanceOf[UnprovenTree].simulated) 0 else 1) } - t.copy(simulated = c < t.k) - case su: UnprovenSchnorr => - // If the node is a leaf, mark it "real'' if the witness for it is available; else mark it "simulated" - val secretKnown = secrets.exists { - case in: DLogProverInput => in.publicImage == su.proposition - case _ => false - } - su.copy(simulated = !secretKnown) - case dhu: UnprovenDiffieHellmanTuple => - // If the node is a leaf, mark it "real" if the witness for it is available; else mark it "simulated" - val secretKnown = secrets.exists { - case in: DiffieHellmanTupleProverInput => in.publicImage == dhu.proposition + t.copy(simulated = realCount < t.k) + // UnprovenSchnorr | UnprovenDiffieHellmanTuple case + case ul: UnprovenLeaf => + // If the node is a leaf, mark it "real'' if either the witness for it is available or a hint shows the secret + // is known to an external participant in multi-signing; + // else mark it "simulated" + val isReal = hintsBag.realImages.contains(ul.proposition) || secrets.exists { + case in: SigmaProtocolPrivateInput[_, _] => in.publicImage == ul.proposition case _ => false } - dhu.copy(simulated = !secretKnown) + ul.withSimulated(!isReal) case t => error(s"Don't know how to markReal($t)") }) @@ -236,11 +198,10 @@ trait ProverInterpreter extends Interpreter with AttributionCore { // We'll mark the first k real ones real val newChildren = t.children.foldLeft((Seq[UnprovenTree](), 0)) { case ((children, countOfReal), child) => val kid = child.asInstanceOf[UnprovenTree] - val (newKid, newCountOfReal) = kid.real match { - case false => (kid, countOfReal) - case true => ( { - if (countOfReal >= t.k) kid.withSimulated(true) else kid - }, countOfReal + 1) + val (newKid, newCountOfReal) = if (kid.real) { + ( { if (countOfReal >= t.k) kid.withSimulated(true) else kid }, countOfReal + 1) + } else { + (kid, countOfReal) } (children :+ newKid, newCountOfReal) }._1 @@ -258,7 +219,7 @@ trait ProverInterpreter extends Interpreter with AttributionCore { * Prover Step 6: For every leaf marked "real", use the first prover step of the Sigma-protocol for that leaf to * compute the commitment a. */ - val simulateAndCommit: Strategy = everywheretd(rule[ProofTree] { + def simulateAndCommit(hintsBag: HintsBag): Strategy = everywheretd(rule[ProofTree] { // Step 4 part 1: If the node is marked "real", then each of its simulated children gets a fresh uniformly // random challenge in {0,1}^t. case and: CAndUnproven if and.real => and // A real AND node has no simulated children @@ -266,8 +227,16 @@ trait ProverInterpreter extends Interpreter with AttributionCore { //real OR or Threshold case case uc: UnprovenConjecture if uc.real => val newChildren = uc.children.cast[UnprovenTree].map(c => - if (c.real) c - else c.withChallenge(Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes)) + if (c.real) { + c + } else { + // take challenge from previously done proof stored in the hints bag, + // or generate random challenge for simulated child + val newChallenge = hintsBag.proofs.find(_.image == c.proposition).map(_.challenge).getOrElse( + Challenge @@ secureRandomBytes(CryptoFunctions.soundnessBytes) + ) + c.withChallenge(newChallenge) + } ) uc match { case or: COrUnproven => or.copy(children = newChildren) @@ -346,36 +315,50 @@ trait ProverInterpreter extends Interpreter with AttributionCore { t.withPolynomial(q).copy(children=newChildren) */ - - case su: UnprovenSchnorr => - if (su.simulated) { - // Step 5 (simulated leaf -- complete the simulation) - assert(su.challengeOpt.isDefined) - val (fm, sm) = DLogInteractiveProver.simulate(su.proposition, su.challengeOpt.get) - UncheckedSchnorr(su.proposition, Some(fm), su.challengeOpt.get, sm) - } else { - // Step 6 (real leaf -- compute the commitment a) - val (r, commitment) = DLogInteractiveProver.firstMessage(su.proposition) - su.copy(commitmentOpt = Some(commitment), randomnessOpt = Some(r)) + // Steps 5 & 6: first try pulling out commitment from the hints bag. If it exists proceed with it, + // otherwise, compute the commitment (if the node is real) or simulate it (if the node is simulated) + + // Step 6 (real leaf -- compute the commitment a or take it from the hints bag) + hintsBag.commitments.find(_.image == su.proposition).map { cmtHint => + su.copy(commitmentOpt = Some(cmtHint.commitment.asInstanceOf[FirstDLogProverMessage])) + }.getOrElse { + if (su.simulated) { + // Step 5 (simulated leaf -- complete the simulation) + assert(su.challengeOpt.isDefined) + val (fm, sm) = DLogInteractiveProver.simulate(su.proposition, su.challengeOpt.get) + UncheckedSchnorr(su.proposition, Some(fm), su.challengeOpt.get, sm) + } else { + // Step 6 -- compute the commitment + val (r, commitment) = DLogInteractiveProver.firstMessage() + su.copy(commitmentOpt = Some(commitment), randomnessOpt = Some(r)) + } } case dhu: UnprovenDiffieHellmanTuple => - if (dhu.simulated) { - // Step 5 (simulated leaf -- complete the simulation) - assert(dhu.challengeOpt.isDefined) - val (fm, sm) = DiffieHellmanTupleInteractiveProver.simulate(dhu.proposition, dhu.challengeOpt.get) - UncheckedDiffieHellmanTuple(dhu.proposition, Some(fm), dhu.challengeOpt.get, sm) - } else { - // Step 6 (real leaf -- compute the commitment a) - val (r, fm) = DiffieHellmanTupleInteractiveProver.firstMessage(dhu.proposition) - dhu.copy(commitmentOpt = Some(fm), randomnessOpt = Some(r)) - } + //Steps 5 & 6: pull out commitment from the hints bag, otherwise, compute the commitment(if the node is real), + // or simulate it (if the node is simulated) + + // Step 6 (real leaf -- compute the commitment a or take it from the hints bag) + hintsBag.commitments.find(_.image == dhu.proposition).map { cmtHint => + dhu.copy(commitmentOpt = Some(cmtHint.commitment.asInstanceOf[FirstDiffieHellmanTupleProverMessage])) + }.getOrElse { + if (dhu.simulated) { + // Step 5 (simulated leaf -- complete the simulation) + assert(dhu.challengeOpt.isDefined) + val (fm, sm) = DiffieHellmanTupleInteractiveProver.simulate(dhu.proposition, dhu.challengeOpt.get) + UncheckedDiffieHellmanTuple(dhu.proposition, Some(fm), dhu.challengeOpt.get, sm) + } else { + // Step 6 -- compute the commitment + val (r, fm) = DiffieHellmanTupleInteractiveProver.firstMessage(dhu.proposition) + dhu.copy(commitmentOpt = Some(fm), randomnessOpt = Some(r)) + } + } case a: Any => error(s"Don't know how to challengeSimulated($a)") }) - def extractChallenge(pt: ProofTree): Option[Array[Byte]] = pt match { + private def extractChallenge(pt: ProofTree): Option[Array[Byte]] = pt match { case upt: UnprovenTree => upt.challengeOpt case sn: UncheckedSchnorr => Some(sn.challenge) case dh: UncheckedDiffieHellmanTuple => Some(dh.challenge) @@ -387,7 +370,7 @@ trait ProverInterpreter extends Interpreter with AttributionCore { * the challenge e for every node marked "real" below the root and, additionally, the response z for every leaf * marked "real" */ - val proving: Strategy = everywheretd(rule[ProofTree] { + def proving(hintsBag: HintsBag): Strategy = everywheretd(rule[ProofTree] { // If the node is a non-leaf marked real whose challenge is e_0, proceed as follows: case and: CAndUnproven if and.real => assert(and.challengeOpt.isDefined) @@ -441,25 +424,76 @@ trait ProverInterpreter extends Interpreter with AttributionCore { t.withPolynomial(q).copy(children = newChildren) // If the node is a leaf marked "real", compute its response according to the second prover step - // of the Sigma-protocol given the commitment, challenge, and witness + // of the Sigma-protocol given the commitment, challenge, and witness, or pull response from the hints bag case su: UnprovenSchnorr if su.real => - assert(su.challengeOpt.isDefined, s"Real UnprovenTree $su should have challenge defined") - val privKey = secrets + assert(su.challengeOpt.isDefined, s"Real UnprovenSchnorr $su should have challenge defined") + val privKeyOpt = secrets .filter(_.isInstanceOf[DLogProverInput]) .find(_.asInstanceOf[DLogProverInput].publicImage == su.proposition) - .get.asInstanceOf[DLogProverInput] - val z = DLogInteractiveProver.secondMessage(privKey, su.randomnessOpt.get, su.challengeOpt.get) + + val z = privKeyOpt match { + case Some(privKey: DLogProverInput) => + hintsBag.ownCommitments.find(_.image == su.proposition).map { oc => + DLogInteractiveProver.secondMessage( + privKey, + oc.secretRandomness, + su.challengeOpt.get) + }.getOrElse { + DLogInteractiveProver.secondMessage( + privKey, + su.randomnessOpt.get, + su.challengeOpt.get) + } + + case None => + hintsBag.realProofs.find(_.image == su.proposition).map { proof => + val provenSchnorr = proof.uncheckedTree.asInstanceOf[UncheckedSchnorr] + provenSchnorr.secondMessage + }.getOrElse { + val bs = secureRandomBytes(32) + SecondDLogProverMessage(new BigInteger(1, bs).mod(CryptoConstants.groupOrder)) + } + } UncheckedSchnorr(su.proposition, None, su.challengeOpt.get, z) + // If the node is a leaf marked "real", compute its response according to the second prover step + // of the Sigma-protocol given the commitment, challenge, and witness, or pull response from the hints bag case dhu: UnprovenDiffieHellmanTuple if dhu.real => - assert(dhu.challengeOpt.isDefined) - val privKey = secrets + assert(dhu.challengeOpt.isDefined, s"Real UnprovenDiffieHellmanTuple $dhu should have challenge defined") + val privKeyOpt = secrets .filter(_.isInstanceOf[DiffieHellmanTupleProverInput]) .find(_.asInstanceOf[DiffieHellmanTupleProverInput].publicImage == dhu.proposition) - .get.asInstanceOf[DiffieHellmanTupleProverInput] - val z = DiffieHellmanTupleInteractiveProver.secondMessage(privKey, dhu.randomnessOpt.get, dhu.challengeOpt.get) + + val z = privKeyOpt match { + case Some(privKey) => + hintsBag.ownCommitments.find(_.image == dhu.proposition).map { oc => + DiffieHellmanTupleInteractiveProver.secondMessage( + privKey.asInstanceOf[DiffieHellmanTupleProverInput], + oc.secretRandomness, + dhu.challengeOpt.get) + }.getOrElse { + DiffieHellmanTupleInteractiveProver.secondMessage( + privKey.asInstanceOf[DiffieHellmanTupleProverInput], + dhu.randomnessOpt.get, + dhu.challengeOpt.get) + } + + case None => + hintsBag.realProofs.find(_.image == dhu.proposition).map { proof => + val provenSchnorr = proof.uncheckedTree.asInstanceOf[UncheckedDiffieHellmanTuple] + provenSchnorr.secondMessage + }.getOrElse { + val bs = secureRandomBytes(32) + SecondDiffieHellmanTupleProverMessage(new BigInteger(1, bs).mod(CryptoConstants.groupOrder)) + } + } UncheckedDiffieHellmanTuple(dhu.proposition, None, dhu.challengeOpt.get, z) + // if the simulated node is proven by someone else, take it from hints bag + case su: UnprovenLeaf if su.simulated => + hintsBag.simulatedProofs.find(_.image == su.proposition).map { proof => + proof.uncheckedTree + }.getOrElse(su) case sn: UncheckedSchnorr => sn @@ -497,6 +531,8 @@ trait ProverInterpreter extends Interpreter with AttributionCore { CThresholdUncheckedNode(t.challengeOpt.get, t.children.map(convertToUnchecked), t.k, t.polynomialOpt) case s: UncheckedSchnorr => s case d: UncheckedDiffieHellmanTuple => d - case _ => ??? + case a: Any => + error(s"Cannot convertToUnproven($a)") } + } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala new file mode 100644 index 0000000000..99b79d442b --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala @@ -0,0 +1,55 @@ +package sigmastate.interpreter + +import java.util + +import scorex.util.encode.Base16 +import sigmastate.Values.Idn +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} + +/** + * Proof of correctness of tx spending + * + * @param proof - proof that satisfies final sigma proposition + * @param extension - user-defined variables to be put into context + */ +class ProverResult(val proof: Array[Byte], val extension: ContextExtension) { + override def hashCode(): Int = util.Arrays.hashCode(proof) * 31 + extension.hashCode() + + override def equals(obj: scala.Any): Boolean = obj match { + case obj: ProverResult => + util.Arrays.equals(proof, obj.proof) && extension == obj.extension + case _ => false + } + + override def toString: Idn = s"ProverResult(${Base16.encode(proof)},$extension)" +} + +object ProverResult { + val empty: ProverResult = ProverResult(Array[Byte](), ContextExtension.empty) + + def apply(proof: Array[Byte], extension: ContextExtension): ProverResult = + new ProverResult(proof, extension) + + object serializer extends SigmaSerializer[ProverResult, ProverResult] { + + override def serialize(obj: ProverResult, w: SigmaByteWriter): Unit = { + w.putUShort(obj.proof.length) + w.putBytes(obj.proof) + ContextExtension.serializer.serialize(obj.extension, w) + } + + override def parse(r: SigmaByteReader): ProverResult = { + val sigBytesCount = r.getUShort() + val proofBytes = r.getBytes(sigBytesCount) + val ce = ContextExtension.serializer.parse(r) + ProverResult(proofBytes, ce) + } + } + +} + + +case class CostedProverResult(override val proof: Array[Byte], + override val extension: ContextExtension, + cost: Long) extends ProverResult(proof, extension) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala new file mode 100644 index 0000000000..40d057bf4d --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverUtils.scala @@ -0,0 +1,66 @@ +package sigmastate.interpreter + +import sigmastate.{ProofTree, SigSerializer, UncheckedConjecture, UncheckedLeaf, UncheckedSigmaTree} +import sigmastate.Values.{ErgoTree, SigmaBoolean} +import sigmastate.basics.VerifierMessage.Challenge + + +trait ProverUtils extends Interpreter { + + /** + * A method which is extracting partial proofs of secret knowledge for particular secrets with their + * respective public images given. Useful for distributed signature applications. + * + * See DistributedSigSpecification for examples of usage. + * + * @param context - context used to reduce the proposition + * @param exp - proposition to reduce + * @param proof - proof for reduced proposition + * @param realSecretsToExtract - public keys of secrets with real proofs + * @param simulatedSecretsToExtract - public keys of secrets with simulated proofs + * @return - bag of OtherSecretProven and OtherCommitment hints + */ + def bagForMultisig(context: CTX, + exp: ErgoTree, + proof: Array[Byte], + realSecretsToExtract: Seq[SigmaBoolean], + simulatedSecretsToExtract: Seq[SigmaBoolean] = Seq.empty): HintsBag = { + + val reducedTree = fullReduction(exp, context, Interpreter.emptyEnv)._1 + + val ut = SigSerializer.parseAndComputeChallenges(reducedTree, proof) + val proofTree = computeCommitments(ut).get.asInstanceOf[UncheckedSigmaTree] + + def traverseNode(tree: ProofTree, + realPropositions: Seq[SigmaBoolean], + simulatedPropositions: Seq[SigmaBoolean], + hintsBag: HintsBag): HintsBag = { + tree match { + case inner: UncheckedConjecture => + inner.children.foldLeft(hintsBag) { case (hb, c) => + traverseNode(c, realPropositions, simulatedPropositions, hb) + } + case leaf: UncheckedLeaf[_] => + val realFound = realPropositions.contains(leaf.proposition) + val simulatedFound = simulatedPropositions.contains(leaf.proposition) + if (realFound || simulatedFound) { + val hints = if (realFound) { + Seq( + RealCommitment(leaf.proposition, leaf.commitmentOpt.get), + RealSecretProof(leaf.proposition, Challenge @@ leaf.challenge, leaf) + ) + } else { + Seq( + SimulatedCommitment(leaf.proposition, leaf.commitmentOpt.get), + SimulatedSecretProof(leaf.proposition, Challenge @@ leaf.challenge, leaf) + ) + } + hintsBag.addHints(hints: _*) + } else hintsBag + } + } + + traverseNode(proofTree, realSecretsToExtract, simulatedSecretsToExtract, HintsBag.empty) + } + +} diff --git a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala index a72ca377cf..5dc00703cd 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala @@ -105,6 +105,7 @@ object Terms { def apply(name: String): Ident = Ident(name, NoType) } + // TODO HF: move to sigmastate.Values case class Apply(func: Value[SType], args: IndexedSeq[Value[SType]]) extends Value[SType] { override def companion = Apply override lazy val tpe: SType = func.tpe match { @@ -173,6 +174,17 @@ object Terms { } object MethodCall extends ValueCompanion { override def opCode: OpCode = OpCodes.MethodCallCode + + /** Helper constructor which allows to cast the resulting node to the specified + * [[sigmastate.Values.Value]] type `T`. + * @see [[sigmastate.lang.Terms.MethodCall]] + */ + def typed[T <: SValue](obj: Value[SType], + method: SMethod, + args: IndexedSeq[Value[SType]], + typeSubst: Map[STypeVar, SType]): T = { + MethodCall(obj, method, args, typeSubst).asInstanceOf[T] + } } object PropertyCall extends ValueCompanion { override def opCode: OpCode = OpCodes.PropertyCallCode diff --git a/sigmastate/src/main/scala/sigmastate/serialization/OpCodes.scala b/sigmastate/src/main/scala/sigmastate/serialization/OpCodes.scala index ef9d5fe068..852507db00 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/OpCodes.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/OpCodes.scala @@ -155,7 +155,7 @@ object OpCodes extends ValueCodes { val CalcBlake2b256Code : OpCode = newOpCode(91) val CalcSha256Code : OpCode = newOpCode(92) val ProveDlogCode : OpCode = newOpCode(93) - val ProveDHTupleCode : OpCode = newOpCode(94) + val ProveDiffieHellmanTupleCode: OpCode = newOpCode(94) val SigmaPropIsProvenCode : OpCode = newOpCode(95) val SigmaPropBytesCode : OpCode = newOpCode(96) val BoolToSigmaPropCode : OpCode = newOpCode(97) diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 3d30dd2fe2..8b88870cc9 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -76,7 +76,7 @@ case class CTHRESHOLD(k: Int, sigmaBooleans: Seq[SigmaBoolean]) extends SigmaBoo override val opCode: OpCode = OpCodes.AtLeastCode } -trait SigmaProofOfKnowledgeTree[SP <: SigmaProtocol[SP], S <: SigmaProtocolPrivateInput[SP, _]] +trait SigmaProofOfKnowledgeLeaf[SP <: SigmaProtocol[SP], S <: SigmaProtocolPrivateInput[SP, _]] extends SigmaBoolean with SigmaProtocolCommonInput[SP] /** Represents boolean values (true/false) in SigmaBoolean tree. @@ -154,7 +154,7 @@ case class CreateProveDHTuple(gv: Value[SGroupElement.type], override def opType = SFunc(IndexedSeq(SGroupElement, SGroupElement, SGroupElement, SGroupElement), SSigmaProp) } object CreateProveDHTuple extends ValueCompanion { - override def opCode: OpCode = OpCodes.ProveDHTupleCode + override def opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode } trait SigmaTransformer[IV <: SigmaPropValue, OV <: SigmaPropValue] extends SigmaPropValue { @@ -174,7 +174,7 @@ case class SigmaAnd(items: Seq[SigmaPropValue]) extends SigmaTransformer[SigmaPr object SigmaAnd extends SigmaTransformerCompanion { override def opCode: OpCode = OpCodes.SigmaAndCode override def argInfos: Seq[ArgInfo] = SigmaAndInfo.argInfos - def apply(head: SigmaPropValue, tail: SigmaPropValue*): SigmaAnd = SigmaAnd(head +: tail) + def apply(first: SigmaPropValue, second: SigmaPropValue, tail: SigmaPropValue*): SigmaAnd = SigmaAnd(Array(first, second) ++ tail) } /** diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 7f8450a828..c7f466d171 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -5,7 +5,7 @@ import java.util import org.ergoplatform._ import org.ergoplatform.validation._ -import scalan.RType +import scalan.{RType, Nullable} import scalan.RType.GeneralType import sigmastate.SType.{TypeCode, AnyOps} import sigmastate.interpreter.CryptoConstants @@ -775,12 +775,13 @@ case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLo override def isConstantSize = true val PropBytes = "propBytes" val IsProven = "isProven" + val PropBytesMethod = SMethod(this, PropBytes, SFunc(this, SByteArray), 1) + .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.") + val IsProvenMethod = SMethod(this, IsProven, SFunc(this, SBoolean), 2) + .withInfo(// available only at frontend of ErgoScript + "Verify that sigma proposition is proven.") protected override def getMethods() = super.getMethods() ++ Seq( - SMethod(this, PropBytes, SFunc(this, SByteArray), 1) - .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree."), - SMethod(this, IsProven, SFunc(this, SBoolean), 2) - .withInfo(// available only at frontend of ErgoScript - "Verify that sigma proposition is proven.") + PropBytesMethod, IsProvenMethod ) } @@ -1293,6 +1294,15 @@ object STuple extends STypeCompanion { } } +/** Helper constuctor/extractor for tuples of two types. */ +object SPair { + def apply(l: SType, r: SType) = STuple(Vector(l, r)) + def unapply(t: STuple): Nullable[(SType, SType)] = t match { + case STuple(IndexedSeq(l, r)) => Nullable((l, r)) + case _ => Nullable.None + } +} + case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypeParam] = Nil) extends SType with SGenericType { @@ -1387,6 +1397,26 @@ case object SBox extends SProduct with SPredefType with SMonoType { val GetReg = "getReg" // should be lazy, otherwise lead to initialization error + lazy val ValueMethod = SMethod(this, Value, SFunc(SBox, SLong), 1) + .withInfo(ExtractAmount, + "Mandatory: Monetary value, in Ergo tokens (NanoErg unit of measure)") + + lazy val PropositionBytesMethod = SMethod(this, PropositionBytes, SFunc(SBox, SByteArray), 2) + .withInfo(ExtractScriptBytes, + "Serialized bytes of guarding script, which should be evaluated to true in order to\n" + + " open this box. (aka spend it in a transaction)") + + lazy val BytesMethod = SMethod(this, Bytes, SFunc(SBox, SByteArray), 3) + .withInfo(ExtractBytes, "Serialized bytes of this box's content, including proposition bytes.") + + lazy val BytesWithoutRefMethod = SMethod(this, BytesWithoutRef, SFunc(SBox, SByteArray), 4) + .withInfo(ExtractBytesWithNoRef, + "Serialized bytes of this box's content, excluding transactionId and index of output.") + + lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5) + .withInfo(ExtractId, + "Blake2b256 hash of this box's content, basically equals to \\lst{blake2b256(bytes)}") + lazy val creationInfoMethod = SMethod(this, CreationInfo, ExtractCreationInfo.OpType, 6) .withInfo(ExtractCreationInfo, """ If \lst{tx} is a transaction which generated this box, then \lst{creationInfo._1} @@ -1406,23 +1436,19 @@ case object SBox extends SProduct with SPredefType with SMonoType { .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Secondary tokens") + // should be lazy to solve recursive initialization protected override def getMethods() = super.getMethods() ++ Vector( - SMethod(this, Value, SFunc(SBox, SLong), 1) - .withInfo(ExtractAmount, "Mandatory: Monetary value, in Ergo tokens (NanoErg unit of measure)"), // see ExtractAmount - SMethod(this, PropositionBytes, SFunc(SBox, SByteArray), 2) - .withInfo(ExtractScriptBytes, "Serialized bytes of guarding script, which should be evaluated to true in order to\n" + - " open this box. (aka spend it in a transaction)"), // see ExtractScriptBytes - SMethod(this, Bytes, SFunc(SBox, SByteArray), 3) - .withInfo(ExtractBytes, "Serialized bytes of this box's content, including proposition bytes."), // see ExtractBytes - SMethod(this, BytesWithoutRef, SFunc(SBox, SByteArray), 4) - .withInfo(ExtractBytesWithNoRef, "Serialized bytes of this box's content, excluding transactionId and index of output."), // see ExtractBytesWithNoRef - SMethod(this, Id, SFunc(SBox, SByteArray), 5) - .withInfo(ExtractId, "Blake2b256 hash of this box's content, basically equals to \\lst{blake2b256(bytes)}"), // see ExtractId + ValueMethod, // see ExtractAmount + PropositionBytesMethod, // see ExtractScriptBytes + BytesMethod, // see ExtractBytes + BytesWithoutRefMethod, // see ExtractBytesWithNoRef + IdMethod, // see ExtractId creationInfoMethod, getRegMethod, tokensMethod ) ++ registers(8) + override val coster = Some(Coster(_.BoxCoster)) } diff --git a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala index 3de68fe459..cb79246333 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala @@ -3,6 +3,11 @@ package sigmastate.utils import java.util import io.circe.Decoder +import org.ergoplatform.settings.ErgoAlgos +import sigmastate.eval.{Colls, SigmaDsl} +import sigmastate.interpreter.CryptoConstants.EcPointType +import special.collection.Coll +import special.sigma.GroupElement import scala.reflect.ClassTag import scala.util.{Failure, Try, Either, Success, Right} @@ -52,7 +57,6 @@ object Helpers { def concatArrays[T:ClassTag](arr1: Array[T], arr2: Array[T]): Array[T] = { val length: Int = arr1.length + arr2.length val result: Array[T] = new Array[T](length) - var pos: Int = 0 System.arraycopy(arr1, 0, result, 0, arr1.length) System.arraycopy(arr2, 0, result, arr1.length, arr2.length) result @@ -128,6 +132,27 @@ object Helpers { } } + /** Decodes the given hex string into byte array and then uses + * [[SigmaDsl.decodePoint()]] to construct [[GroupElement]] instance. + */ + def decodeGroupElement(hexString: String): GroupElement = { + val bytes = ErgoAlgos.decodeUnsafe(hexString) + SigmaDsl.decodePoint(Colls.fromArray(bytes)) + } + + /** Decodes the given hex string into [[GroupElement]] and then extracts the underlying + * [[EcPointType]] instance + */ + def decodeECPoint(hexString: String): EcPointType = { + val ge = decodeGroupElement(hexString) + SigmaDsl.toECPoint(ge).asInstanceOf[EcPointType] + } + + /** Decodes the given hex string into a collection of bytes. */ + def decodeBytes(base16String: String): Coll[Byte] = { + val bytes = ErgoAlgos.decodeUnsafe(base16String) + Colls.fromArray(bytes) + } } object Overloading { diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index d9146ded39..39b4b28138 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -145,6 +145,9 @@ case class SelectField(input: Value[STuple], fieldIndex: Byte) } object SelectField extends ValueCompanion { override def opCode: OpCode = OpCodes.SelectFieldCode + def typed[T <: SValue](input: Value[STuple], fieldIndex: Byte): T = { + SelectField(input, fieldIndex).asInstanceOf[T] + } } /** Represents execution of Sigma protocol that validates the given input SigmaProp. */ diff --git a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala index 0e32779f5c..6715e3cd1c 100644 --- a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala @@ -29,7 +29,7 @@ class CostingSpecification extends SigmaTestingData { val printCosts = true - val (key1, _, avlProver) = sampleAvlProver + val (key1, _, _, avlProver) = sampleAvlProver val keys = Colls.fromItems(key1) val key2 = keyCollGen.sample.get avlProver.performOneOperation(Lookup(ADKey @@ key1.toArray)) diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala new file mode 100644 index 0000000000..ec69ff8d39 --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -0,0 +1,259 @@ +package sigmastate + +import org.ergoplatform.validation.ValidationException +import special.sigma.SigmaTestingData + +/** Regression tests with ErgoTree related test vectors. + * This test vectors verify various constants which are consensus critical and should not change. + */ +class ErgoTreeSpecification extends SigmaTestingData { + + val typeCodes = Table( + ("constant", "expectedValue"), + (SPrimType.LastPrimTypeCode, 8), + (SPrimType.MaxPrimTypeCode, 11) + ) + + property("Expected values of constants") { + forAll(typeCodes) { (const, expValue) => + const shouldBe expValue + } + } + + // Expected meta-parameters of predefined types (see Predefined Types section in docs/spec/spec.pdf) + val types = Table( + ("type", "Code", "IsConst", "IsPrim", "IsEmbed", "IsNum"), + (SBoolean, 1, true, true, true, false), + (SByte, 2, true, true, true, true), + (SShort, 3, true, true, true, true), + (SInt, 4, true, true, true, true), + (SLong, 5, true, true, true, true), + (SBigInt, 6, true, true, true, true), + (SGroupElement, 7, true, true, true, false), + (SSigmaProp, 8, true, true, true, false), + (SBox, 99, false, false, false, false), + (SAvlTree, 100, true, false, false, false), + (SContext, 101, false, false, false, false), + (SHeader, 104, true, false, false, false), + (SPreHeader, 105, true, false, false, false), + (SGlobal, 106, true, false, false, false) + ) + + property("Predefined Types") { + forAll(types) { (t, code, isConst, isPrim, isEmbed, isNum) => + t.typeCode shouldBe code + t.typeId shouldBe code + t.isConstantSize shouldBe isConst + t.isInstanceOf[SPrimType] shouldBe isPrim + t.isEmbeddable shouldBe isEmbed + t.isNumType shouldBe isNum + whenever(isPrim) { + t.typeCode should be <= SPrimType.LastPrimTypeCode + } + } + } + + /** Expected parameters of resolved method (see `methods` table below). + * + * @param isResolvableFromIds if true, them SMethod.fromIds must resolve, otherwise + * ValidationException must be thrown + */ + case class MInfo(methodId: Byte, method: SMethod, isResolvableFromIds: Boolean = true) + + // NOTE, the type code constants are checked above + // The methodId codes as checked here, they MUST be PRESERVED. + // The following table should be made dependent on HF activation + val methods = Table( + ("typeId", "methods", "CanHaveMethods"), + (SBoolean.typeId, Seq.empty[MInfo], true), + (SByte.typeId, Seq.empty[MInfo], false), + (SShort.typeId, Seq.empty[MInfo], false), + (SInt.typeId, Seq.empty[MInfo], false), + (SLong.typeId, Seq.empty[MInfo], false), + + { // SNumericType.typeId is erroneously shadowed by SGlobal.typeId + // this should be preserved in 3.x and fixed in 4.0 + (SNumericType.typeId, Seq( + MInfo(methodId = 1, SGlobal.groupGeneratorMethod), + MInfo(2, SGlobal.xorMethod) + ), true) + }, + + { // SBigInt inherit methods from SNumericType.methods + // however they are not resolvable via SBigInt.typeId + import SNumericType._ + (SBigInt.typeId, Seq( + MInfo(methodId = 1, ToByteMethod, isResolvableFromIds = false), + MInfo(2, ToShortMethod, isResolvableFromIds = false), + MInfo(3, ToIntMethod, isResolvableFromIds = false), + MInfo(4, ToLongMethod, isResolvableFromIds = false), + MInfo(5, ToBigIntMethod, isResolvableFromIds = false), + MInfo(6, ToBytesMethod, isResolvableFromIds = false), + MInfo(7, ToBitsMethod, isResolvableFromIds = false) + ), true) + }, + { import SGroupElement._ + (SGroupElement.typeId, Seq( + MInfo(2, GetEncodedMethod), + MInfo(3, ExponentiateMethod), + MInfo(4, MultiplyMethod), + MInfo(5, NegateMethod) + ), true) + }, + { import SSigmaProp._ + (SSigmaProp.typeId, Seq( + MInfo(1, PropBytesMethod), + MInfo(2, IsProvenMethod) // TODO HF: this method must be removed + ), true) + }, + { import SBox._ + (SBox.typeId, Seq( + MInfo(1, ValueMethod), + MInfo(2, PropositionBytesMethod), + MInfo(3, BytesMethod), + MInfo(4, BytesWithoutRefMethod), + MInfo(5, IdMethod), + MInfo(6, creationInfoMethod), + MInfo(7, getRegMethod), + MInfo(8, tokensMethod) + ) ++ registers(idOfs = 8) + .zipWithIndex + .map { case (m,i) => MInfo((8 + i + 1).toByte, m) }, true) + }, + { import SAvlTree._ + (SAvlTree.typeId, Seq( + MInfo(1, digestMethod), + MInfo(2, enabledOperationsMethod), + MInfo(3, keyLengthMethod), + MInfo(4, valueLengthOptMethod), + MInfo(5, isInsertAllowedMethod), + MInfo(6, isUpdateAllowedMethod), + MInfo(7, isRemoveAllowedMethod), + MInfo(8, updateOperationsMethod), + MInfo(9, containsMethod), + MInfo(10, getMethod), + MInfo(11, getManyMethod), + MInfo(12, insertMethod), + MInfo(13, updateMethod), + MInfo(14, removeMethod), + MInfo(15, updateDigestMethod) + ), true) + }, + { import SHeader._ + (SHeader.typeId, Seq( + MInfo(1, idMethod), MInfo(2, versionMethod), MInfo(3, parentIdMethod), + MInfo(4, ADProofsRootMethod), MInfo(5, stateRootMethod), MInfo(6, transactionsRootMethod), + MInfo(7, timestampMethod), MInfo(8, nBitsMethod), MInfo(9, heightMethod), + MInfo(10, extensionRootMethod), MInfo(11, minerPkMethod), MInfo(12, powOnetimePkMethod), + MInfo(13, powNonceMethod), MInfo(14, powDistanceMethod), MInfo(15, votesMethod) + ), true) + }, + { import SPreHeader._ + (SPreHeader.typeId, Seq( + MInfo(1, versionMethod), MInfo(2, parentIdMethod), MInfo(3, timestampMethod), + MInfo(4, nBitsMethod), MInfo(5, heightMethod), MInfo(6, minerPkMethod), + MInfo(7, votesMethod) + ), true) + }, + { import SContext._ + (SContext.typeId, Seq( + MInfo(1, dataInputsMethod), MInfo(2, headersMethod), MInfo(3, preHeaderMethod), + MInfo(4, inputsMethod), MInfo(5, outputsMethod), MInfo(6, heightMethod), + MInfo(7, selfMethod), MInfo(8, selfBoxIndexMethod), MInfo(9, lastBlockUtxoRootHashMethod), + MInfo(10, minerPubKeyMethod), MInfo(11, getVarMethod) + ), true) + }, + { import SGlobal._ + (SGlobal.typeId, Seq( + MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) + ), true) + }, + { import SCollection._ + (SCollection.typeId, Seq( + MInfo(1, SizeMethod), + MInfo(2, GetOrElseMethod), + MInfo(3, MapMethod), + MInfo(4, ExistsMethod), + MInfo(5, FoldMethod), + MInfo(6, ForallMethod), + MInfo(7, SliceMethod), + MInfo(8, FilterMethod), + MInfo(9, AppendMethod), + MInfo(10, ApplyMethod), + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + BitShiftLeftMethod, + BitShiftRightMethod, + BitShiftRightZeroedMethod, + */ + MInfo(14, IndicesMethod), + MInfo(15, FlatMapMethod), + MInfo(19, PatchMethod), + MInfo(20, UpdatedMethod), + MInfo(21, UpdateManyMethod), + /*TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + UnionSetsMethod, + DiffMethod, + IntersectMethod, + PrefixLengthMethod, + */ + MInfo(26, IndexOfMethod), + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + LastIndexOfMethod, + FindMethod, + */ + MInfo(29, ZipMethod) + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + DistinctMethod, + StartsWithMethod, + EndsWithMethod, + MapReduceMethod, + */ + ), true) + }, + { import SOption._ + (SOption.typeId, Seq( + MInfo(2, IsDefinedMethod), + MInfo(3, GetMethod), + MInfo(4, GetOrElseMethod), + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + FoldMethod, + */ + MInfo(7, MapMethod), + MInfo(8, FilterMethod) + ), true) + } + ) + + property("MethodCall Codes") { + forAll(methods) { (typeId, methods, canHaveMethods) => + SType.types.get(typeId) match { + case Some(tyDesc) => + assert(canHaveMethods, s"Type $tyDesc should NOT have methods") + + tyDesc.methods.length shouldBe methods.length + for (expectedMethod <- methods) { + if (expectedMethod.isResolvableFromIds) { + + // the following line is used in MethodCall deserializer to resolve SMethod + val resolvedMethod = SMethod.fromIds(typeId, expectedMethod.methodId) + + resolvedMethod.objType.typeId shouldBe typeId + resolvedMethod.name shouldBe expectedMethod.method.name + resolvedMethod.irInfo shouldBe expectedMethod.method.irInfo + } else { + // declared, but not supported + assertExceptionThrown( + SMethod.fromIds(typeId, expectedMethod.methodId), + { case _: ValidationException => true + case _ => false }, + s"MethodCall shouldn't resolve for typeId = $typeId and $expectedMethod" + ) + } + } + case None => + assert(!canHaveMethods, s"Type with code $typeId can have methods") + } + } + + } +} diff --git a/sigmastate/src/test/scala/sigmastate/FailingToProveSpec.scala b/sigmastate/src/test/scala/sigmastate/FailingToProveSpec.scala index 475e52f020..ac846b2b6c 100644 --- a/sigmastate/src/test/scala/sigmastate/FailingToProveSpec.scala +++ b/sigmastate/src/test/scala/sigmastate/FailingToProveSpec.scala @@ -1,5 +1,7 @@ package sigmastate +import org.ergoplatform.{ErgoBox, ErgoLikeContext} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestInterpreter, SigmaTestingCommons} import org.ergoplatform.{ErgoBox, ErgoLikeContext, ErgoLikeInterpreter, ErgoLikeTransaction} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} import sigmastate.lang.Terms._ diff --git a/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala index f2cd2ca808..b77d3c5b0a 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala @@ -2,33 +2,56 @@ package sigmastate.crypto import java.math.BigInteger +import org.scalacheck.Gen import sigmastate.helpers.SigmaTestingCommons import sigmastate.interpreter.CryptoConstants +import sigmastate.interpreter.CryptoConstants.EcPointType + +import scala.util.Random class GroupLawsSpecification extends SigmaTestingCommons { private val group = CryptoConstants.dlogGroup - property("multiplication law is complete") { - val identity = group.identity - val ge = group.createRandomGenerator() - - group.multiplyGroupElements(ge, ge) shouldBe group.exponentiate(ge, new BigInteger("2")) - group.multiplyGroupElements(ge, identity) shouldBe group.exponentiate(ge, BigInteger.ONE) - group.multiplyGroupElements(ge, identity) shouldBe ge - group.multiplyGroupElements(identity, identity) shouldBe identity + val groupElementGen: Gen[EcPointType] = Gen.const(group.createRandomElement()) + val groupGeneratorGen: Gen[EcPointType] = Gen.const(group.createRandomGenerator()) + val bigIntGen: Gen[BigInteger] = Gen.const{ + val bytes = Array.fill(32)(Random.nextInt(Byte.MaxValue).toByte) + new BigInteger(1, bytes).mod(group.order) + } - val inverse = group.getInverse(ge) - group.multiplyGroupElements(ge, inverse) shouldBe identity + property("multiplication law is complete") { + forAll(groupElementGen) { ge => + val identity = group.identity + group.multiplyGroupElements(ge, ge) shouldBe group.exponentiate(ge, new BigInteger("2")) + group.multiplyGroupElements(ge, identity) shouldBe group.exponentiate(ge, BigInteger.ONE) + group.multiplyGroupElements(ge, identity) shouldBe ge + group.multiplyGroupElements(identity, identity) shouldBe identity + + val inverse = group.inverseOf(ge) + group.multiplyGroupElements(ge, inverse) shouldBe identity + } } property("exponentiation") { val identity = group.identity - val ge = group.createRandomGenerator() + forAll(groupElementGen) { ge => + group.exponentiate(ge, BigInteger.ZERO) shouldBe identity + group.exponentiate(ge, BigInteger.ONE) shouldBe ge + group.exponentiate(ge, group.order) shouldBe identity + group.exponentiate(ge, group.order.add(BigInteger.ONE)) shouldBe ge + } + } + + property("double inverse") { + forAll(groupElementGen) { ge => + group.inverseOf(group.inverseOf(ge)) shouldBe ge + } + } - group.exponentiate(ge, BigInteger.ZERO) shouldBe identity - group.exponentiate(ge, BigInteger.ONE) shouldBe ge - group.exponentiate(ge, group.order) shouldBe identity - group.exponentiate(ge, group.order.add(BigInteger.ONE)) shouldBe ge + property("precomputed"){ + forAll(groupGeneratorGen, bigIntGen) { case (base, exp) => + group.exponentiateWithPreComputedValues(base, exp) shouldBe group.exponentiate(base, exp) + } } } diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala b/sigmastate/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala index ccd8c0d467..f88d30ddcc 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala @@ -3,7 +3,7 @@ package sigmastate.helpers import sigmastate.SType import sigmastate.Values.{ErgoTree, EvaluatedValue} import sigmastate.interpreter.Interpreter.ScriptEnv -import sigmastate.interpreter.{ContextExtension, CostedProverResult, ProverInterpreter} +import sigmastate.interpreter.{ContextExtension, CostedProverResult, HintsBag, ProverInterpreter} import scala.util.Try @@ -24,7 +24,7 @@ trait ContextEnrichingProverInterpreter extends ProverInterpreter { /** * Replace context.extension to knownExtensions and prove script in different context. */ - override def prove(env: ScriptEnv, exp: ErgoTree, context: CTX, message: Array[Byte]): Try[CostedProverResult] = { + override def prove(env: ScriptEnv, exp: ErgoTree, context: CTX, message: Array[Byte], hintsBag: HintsBag): Try[CostedProverResult] = { val enrichedContext = context.withExtension(knownExtensions).asInstanceOf[CTX] super.prove(env, exp, enrichedContext, message) } diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala new file mode 100644 index 0000000000..03721c684c --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -0,0 +1,217 @@ +package sigmastate.helpers + +import java.math.BigInteger + +import org.ergoplatform.ErgoBox +import org.ergoplatform.ErgoBox.RegisterId +import org.ergoplatform.settings.ErgoAlgos + +import scala.collection.mutable +import pprint.{Tree, PPrinter} +import scalan.RType +import scalan.RType.PrimitiveType +import sigmastate.SCollection._ +import sigmastate.Values.{ValueCompanion, ConstantNode, ErgoTree} +import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.lang.SigmaTyper +import sigmastate.lang.Terms.MethodCall +import sigmastate.serialization.GroupElementSerializer +import sigmastate.utxo.SelectField +import sigmastate._ +import special.collection.Coll +import special.sigma.GroupElement + +import scala.collection.mutable.ArrayBuffer +import scala.reflect.ClassTag + +/** Pretty-printer customized to print [[sigmastate.Values.Value]] instances + * into a valid Scala code (can be cut-and-pasted).*/ +object SigmaPPrint extends PPrinter { + + /** Apply [[treeify]] for each element of the given sequence producing the iterator of resulting trees. */ + protected def treeifySeq(xs: Seq[Any]): Iterator[Tree] = { + xs.iterator.map(_ match { + case t: Tree => t + case x => treeify(x) + }) + } + + /** Helper overload to call [[treeifySeq]]. */ + protected def treeifyMany(head: Any, tail: Any*): Iterator[Tree] = { + treeifySeq(head +: tail) + } + + private def tpeName(tpe: SType): String = { + val name = tpe.toTermString + if (name == "Boolean") "Bool" else name + } + + /** Valid Scala type for the given SType. */ + private[helpers] def typeName(tpe: SType): String = tpe match { + case _: SPredefType => + val name = tpe.getClass.getSimpleName.replace("$", "") + s"$name.type" // SByte.type, SInt.type, etc + case ct: SCollectionType[_] => + s"SCollection[${typeName(ct.elemType)}]" + case ot: SOption[_] => + s"SOption[${typeName(ot.elemType)}]" + case _: STuple => + "STuple" + case _ => + sys.error(s"Cannot get typeName($tpe)") + } + + /** Valid Scala type of the Value with the given underlying SType. */ + private def valueType(tpe: SType): String = { + val tn = typeName(tpe) + s"Value[$tn]" + } + + private val typeHandlers: PartialFunction[Any, Tree] = { + case SByteArray => + Tree.Literal("SByteArray") + case SByteArray2 => + Tree.Literal("SByteArray2") + case SBooleanArray => + Tree.Literal("SBooleanArray") + case SPair(l, r) => + Tree.Apply("SPair", treeifySeq(Array(l, r))) + case t: PrimitiveType[_] => + Tree.Literal(s"RType.${t.name}Type") + } + + private val exceptionHandlers: PartialFunction[Any, Tree] = { + case ex: Exception => + Tree.Apply(s"new ${ex.getClass.getSimpleName}", treeifySeq(Seq(ex.getMessage))) + } + + /** Generated Scala code which creates the given byte array from a hex string literal. */ + private def treeifyByteArray(bytes: Array[Byte]): Tree = { + val hexString = ErgoAlgos.encode(bytes) + Tree.Apply("ErgoAlgos.decodeUnsafe", treeifyMany(hexString)) + } + + private val dataHandlers: PartialFunction[Any, Tree] = { + case v: Byte => + Tree.Literal(s"$v.toByte") + + case v: Short => + Tree.Literal(s"$v.toShort") + + case v: BigInteger => + Tree.Apply("new BigInteger", treeifyMany(v.toString(16), 16)) + + case wa: mutable.WrappedArray[Byte @unchecked] if wa.elemTag == ClassTag.Byte => + treeifyByteArray(wa.array) + + case wa: mutable.WrappedArray[_] => + Tree.Apply("Array", treeifySeq(wa)) + + case arr: Array[Byte @unchecked] if arr.elemTag == ClassTag.Byte => + treeifyByteArray(arr) + + case arr: Array[_] => + Tree.Apply("Array", treeifySeq(arr)) + + case buf: ArrayBuffer[_] => + Tree.Apply("Seq", treeifySeq(buf)) + + case ecp: EcPointType => + val hexString = ErgoAlgos.encode(GroupElementSerializer.toBytes(ecp)) + Tree.Apply("Helpers.decodeECPoint", treeifyMany(hexString)) + + case ge: GroupElement => + val hexString = ErgoAlgos.encode(ge.getEncoded) + Tree.Apply("Helpers.decodeGroupElement", treeifyMany(hexString)) + + case coll: Coll[Byte @unchecked] if coll.tItem == RType.ByteType => + val hexString = ErgoAlgos.encode(coll) + Tree.Apply("Helpers.decodeBytes", treeifyMany(hexString)) + + case coll: Coll[_] => + val elemTpe = coll.tItem.name + Tree.Apply(s"Coll[$elemTpe]", treeifySeq(coll.toArray)) + + case t: AvlTreeData => + Tree.Apply("AvlTreeData", treeifyMany( + Tree.Apply("ADDigest @@ ", treeifyMany(t.digest)), + t.treeFlags, + t.keyLength, + t.valueLengthOpt)) + + case t: ErgoTree => + Tree.Apply("new ErgoTree", treeifyMany( + t.header, + Tree.Apply("Vector", t.constants.map(treeify).iterator), + t.root + )) + + case b: ErgoBox => + val tokens = Tree.Apply("Coll", + b.additionalTokens.toArray.map { case (id, v) => + val idTree = Tree.Apply("Digest32 @@ ", treeifyMany(id)) + Tree.Apply("", treeifyMany(idTree, v)) + }.iterator) + Tree.Apply("new ErgoBox", treeifyMany( + b.value, + b.ergoTree, + tokens, + b.additionalRegisters, + Tree.Apply("ModifierId @@ ", treeifyMany(b.transactionId)), + b.index, + b.creationHeight + )) + } + + override val additionalHandlers: PartialFunction[Any, Tree] = + typeHandlers + .orElse(exceptionHandlers) + .orElse(dataHandlers) + .orElse { + case sigmastate.SGlobal => + Tree.Literal(s"SGlobal") + case sigmastate.SCollection => + Tree.Literal(s"SCollection") + case sigmastate.SOption => + Tree.Literal(s"SOption") + case t: STypeCompanion if t.isInstanceOf[SType] => + Tree.Literal(s"S${t.typeName}") + case c: ValueCompanion => + Tree.Literal(c.typeName) + case r: RegisterId => + Tree.Literal(s"ErgoBox.R${r.number}") + case sf: SelectField => + val resTpe = sf.input.tpe.items(sf.fieldIndex - 1) + val resTpeName = valueType(resTpe) + Tree.Apply(s"SelectField.typed[$resTpeName]", treeifySeq(Array(sf.input, sf.fieldIndex))) + + case ConstantNode(v, SCollectionType(elemType)) if elemType.isInstanceOf[SPredefType] => + Tree.Apply(tpeName(elemType) + "ArrayConstant", treeifySeq(Seq(v))) + + case c: ConstantNode[_] if c.tpe.isInstanceOf[SPredefType] => + Tree.Apply(tpeName(c.tpe) + "Constant", treeifySeq(Seq(c.value))) + + case ArithOp(l, r, code) => + val args = treeifySeq(Seq(l, r)).toSeq :+ Tree.Apply("OpCode @@ ", treeifySeq(Seq(code))) + Tree.Apply("ArithOp", args.iterator) + case mc @ MethodCall(obj, method, args, typeSubst) => + val objType = apply(method.objType).plainText + val methodTemplate = method.objType.getMethodByName(method.name) + val methodT = SigmaTyper.unifyTypeLists(methodTemplate.stype.tDom, obj.tpe +: args.map(_.tpe)) match { + case Some(subst) if subst.nonEmpty => + val getMethod = s"""$objType.getMethodByName("${method.name}").withConcreteTypes""" + Tree.Apply(getMethod, treeifySeq(Seq(subst))) + case _ => + val getMethod = s"$objType.getMethodByName" + Tree.Apply(getMethod, Seq(treeify(method.name)).iterator) + } + + val objT = treeify(obj) + val argsT = treeify(args) + val substT = treeify(typeSubst) + val resTpe = mc.tpe + val resTpeName = valueType(resTpe) + Tree.Apply(s"MethodCall.typed[$resTpeName]", Seq(objT, methodT, argsT, substT).iterator) + } +} + diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala new file mode 100644 index 0000000000..d629d379a1 --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala @@ -0,0 +1,182 @@ +package sigmastate.helpers + +import java.math.BigInteger + +import org.ergoplatform.settings.ErgoAlgos +import org.ergoplatform.{Outputs, ErgoBox} +import scalan.RType +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.Digest32 +import scorex.util.ModifierId +import sigmastate.Values._ +import sigmastate.lang.Terms.MethodCall +import sigmastate.serialization.OpCodes +import sigmastate.utxo.SelectField +import sigmastate._ +import sigmastate.eval._ +import sigmastate.utils.Helpers +import special.collection.CollType +import special.sigma.SigmaDslTesting + +import scala.collection.mutable.ArrayBuffer + +class SigmaPPrintSpec extends SigmaDslTesting { + + + property("typeName") { + def test(t: SType, exp: String) = { + SigmaPPrint.typeName(t) shouldBe exp + } + test(SInt, "SInt.type") + test(SCollection(SBox), "SCollection[SBox.type]") + test(SOption(SBox), "SOption[SBox.type]") + test(STuple(SBox, SInt), "STuple") + } + + property("Special cases") { + def test(x: Any, expected: String) = { + val res = SigmaPPrint(x).plainText + res shouldBe expected + } + + // type handlers + test(SCollectionType(SByte), "SByteArray") + test(SCollectionType(SCollectionType(SByte)), "SByteArray2") + test(SCollectionType(SBoolean), "SBooleanArray") + test(STuple(Vector(SBoolean, SInt)), "SPair(SBoolean, SInt)") + + test(RType.BooleanType, "RType.BooleanType") + test(CollType(RType.ByteType), "CollType(RType.ByteType)") + + // exception handlers + test(new ArithmeticException("msg"), "new ArithmeticException(\"msg\")") + + // data handlers + test(10.toByte, "10.toByte") + test(255.toByte, "-1.toByte") + test(10.toShort, "10.toShort") + + test(new BigInteger("a", 16), """new BigInteger("a", 16)""") + + val negative = new BigInteger("-a", 16) + negative.toString(10) shouldBe "-10" + test(negative, """new BigInteger("-a", 16)""") + + test(ErgoAlgos.decodeUnsafe("00ffaa"), "ErgoAlgos.decodeUnsafe(\"00ffaa\")") + test(ErgoAlgos.decodeUnsafe("00ffaa"): Seq[Byte], "ErgoAlgos.decodeUnsafe(\"00ffaa\")") + + test(Array(10), "Array(10)") + test(Array(10): Seq[Int], "Array(10)") + test({val buf = ArrayBuffer.empty[Int]; buf += (10); buf}, "Seq(10)") + test(Helpers.decodeBytes("00ff"), "Helpers.decodeBytes(\"00ff\")") + val ge1 = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" + + test(Helpers.decodeGroupElement(ge1), s"""Helpers.decodeGroupElement("${ge1}")""") + test(Helpers.decodeECPoint(ge1), s"""Helpers.decodeECPoint("${ge1}")""") + + val t1 = AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), + AvlTreeFlags(false, true, true), + 1, + Some(1) + ) + test(t1, + """AvlTreeData( + | ADDigest @@ ( + | ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094") + | ), + | AvlTreeFlags(false, true, true), + | 1, + | Some(1) + |)""".stripMargin) + test( + new ErgoTree( + 16.toByte, + Vector(IntArrayConstant(Array(10, 20))), + Right(BoolToSigmaProp(TrueLeaf)) + ), + """new ErgoTree( + | 16.toByte, + | Vector(IntArrayConstant(Coll[Int](10, 20))), + | Right(BoolToSigmaProp(TrueLeaf)) + |)""".stripMargin) + test( + CostingBox( + false, + new ErgoBox( + 9223372036854775807L, + new ErgoTree(0.toByte, Vector(), Right(BoolToSigmaProp(FalseLeaf))), + Coll( + (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), 10000000L) + ), + Map(), + ModifierId @@ ("bc80ffc00100d60101ffd3d3ab7f73800aff80487fff7fffbb010080ff7f0837"), + 0.toShort, + 1000000 + ) + ), + """CostingBox( + | false, + | new ErgoBox( + | 9223372036854775807L, + | new ErgoTree(0.toByte, Vector(), Right(BoolToSigmaProp(FalseLeaf))), + | Coll( + | ( + | Digest32 @@ ( + | ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001") + | ), + | 10000000L + | ) + | ), + | Map(), + | ModifierId @@ ("bc80ffc00100d60101ffd3d3ab7f73800aff80487fff7fffbb010080ff7f0837"), + | 0.toShort, + | 1000000 + | ) + |)""".stripMargin + ) + // additionalHandlers + test(SGlobal, "SGlobal") + test(SCollection, "SCollection") + test(SOption, "SOption") + test(SInt, "SInt") + test(Outputs, "Outputs") + test(ErgoBox.R0, "ErgoBox.R0") + test(ErgoBox.R9, "ErgoBox.R9") + test( + SelectField.typed[Value[SByte.type]](ValUse(1, STuple(Vector(SByte, SByte))), 1.toByte), + "SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)), 1.toByte)" + ) + test(IntConstant(10), "IntConstant(10)") + test(ArithOp(IntConstant(1), IntConstant(1), OpCodes.PlusCode), "ArithOp(IntConstant(1), IntConstant(1), OpCode @@ (-102.toByte))") + test( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + """MethodCall.typed[Value[SCollection[SBox.type]]]( + | ValUse(1, SContext), + | SContext.getMethodByName("dataInputs"), + | Vector(), + | Map() + |)""".stripMargin) + + test(SCollection.tIV, """STypeVar("IV")""") + test(Map(SCollection.tIV -> SInt), """Map(STypeVar("IV") -> SInt)""") + test( + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.IndicesMethod.withConcreteTypes(Map(SCollection.tIV -> SBox)), + Vector(), + Map() + ), + """MethodCall.typed[Value[SCollection[SInt.type]]]( + | ValUse(1, SCollectionType(SBox)), + | SCollection.getMethodByName("indices").withConcreteTypes(Map(STypeVar("IV") -> SBox)), + | Vector(), + | Map() + |)""".stripMargin) + } +} diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala index 0589eeeb1f..ef94329e31 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala @@ -2,33 +2,29 @@ package sigmastate.helpers import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix import org.ergoplatform.ErgoBox.NonMandatoryRegisterId -import org.ergoplatform.SigmaConstants.ScriptCostLimit -import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform.ErgoScriptPredef.TrueProp import org.ergoplatform._ -import org.ergoplatform.validation.{SigmaValidationSettings, ValidationRules, ValidationSpecification} +import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc} +import org.ergoplatform.validation.ValidationSpecification import org.scalacheck.Arbitrary.arbByte import org.scalacheck.Gen -import org.scalatest.prop.{GeneratorDrivenPropertyChecks, PropertyChecks} -import org.scalatest.{Assertion, Matchers, PropSpec} -import scalan.{RType, TestContexts, TestUtils} -import scorex.crypto.hash.{Blake2b256, Digest32} +import org.scalatest.prop.{PropertyChecks, GeneratorDrivenPropertyChecks} +import org.scalatest.{PropSpec, Assertion, Matchers} +import scalan.{TestUtils, TestContexts, RType} +import scorex.crypto.hash.{Digest32, Blake2b256} import sigma.types.IsPrimView -import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue, GroupElementConstant, SValue, Value} -import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} -import sigmastate.interpreter.{ContextExtension, CryptoConstants, Interpreter} -import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder} -import sigmastate.serialization.{GroupElementSerializer, SigmaSerializer, ValueSerializer} -import sigmastate.{AvlTreeData, SGroupElement, SType} -import sigmastate.eval.{CompiletimeCosting, Evaluation, IRContext, _} +import sigmastate.Values.{Constant, EvaluatedValue, SValue, Value, ErgoTree, GroupElementConstant} +import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv} +import sigmastate.interpreter.{CryptoConstants, Interpreter} +import sigmastate.lang.{Terms, TransformingSigmaBuilder, SigmaCompiler} +import sigmastate.serialization.{ValueSerializer, SigmaSerializer} +import sigmastate.{SGroupElement, SType} +import sigmastate.eval.{CompiletimeCosting, IRContext, Evaluation, _} import sigmastate.interpreter.CryptoConstants.EcPointType -import special.collection.Coll import special.sigma -import special.sigma.{Box, Header, PreHeader} import scala.annotation.tailrec import scala.language.implicitConversions -import scala.util.Try trait SigmaTestingCommons extends PropSpec with PropertyChecks @@ -136,7 +132,34 @@ trait SigmaTestingCommons extends PropSpec } } - def func[A: RType, B: RType](func: String, bindings: (Byte, EvaluatedValue[_ <: SType])*)(implicit IR: IRContext): A => B = { + case class CompiledFunc[A,B] + (script: String, bindings: Seq[(Byte, EvaluatedValue[_ <: SType])], expr: SValue, func: A => B) + (implicit val tA: RType[A], val tB: RType[B]) extends Function1[A, B] { + override def apply(x: A): B = func(x) + } + + /** The same operations are executed as part of Interpreter.verify() */ + def getCostingResult(env: ScriptEnv, exp: SValue)(implicit IR: IRContext): IR.RCostingResultEx[Any] = { + val costingRes = IR.doCostingEx(env, exp, true) + val costF = costingRes.costF + CheckCostFunc(IR)(IR.asRep[Any => Int](costF)) + + val calcF = costingRes.calcF + CheckCalcFunc(IR)(calcF) + costingRes + } + + /** Returns a Scala function which is equivalent to the given function script. + * The script is embedded into valid ErgoScript which is then compiled to + * [[sigmastate.Values.Value]] tree. + * Limitations: + * 1) DeserializeContext, ConstantPlaceholder is not supported + * @param funcScript source code of the function + * @param bindings additional context variables + */ + def func[A: RType, B: RType] + (funcScript: String, bindings: (Byte, EvaluatedValue[_ <: SType])*) + (implicit IR: IRContext): CompiledFunc[A, B] = { import IR._ import IR.Context._; val tA = RType[A] @@ -145,33 +168,77 @@ trait SigmaTestingCommons extends PropSpec val tpeB = Evaluation.rtypeToSType(tB) val code = s"""{ - | val func = $func + | val func = $funcScript | val res = func(getVar[${tA.name}](1).get) | res |} """.stripMargin val env = Interpreter.emptyEnv - val interProp = compiler.typecheck(env, code) - val IR.Pair(calcF, _) = IR.doCosting[Any](env, interProp) - val tree = IR.buildTree(calcF) - checkSerializationRoundTrip(tree) - val lA = Liftables.asLiftable[SContext, IR.Context](calcF.elem.eDom.liftable) - val lB = Liftables.asLiftable[Any, Any](calcF.elem.eRange.liftable) - val valueFun = IR.compile[SContext, Any, IR.Context, Any](IR.getDataEnv, calcF)(lA, lB) - (in: A) => { + // The following ops are performed by frontend + // typecheck, create graphs, compile to Tree + // The resulting tree should be serializable + val compiledTree = { + val internalProp = compiler.typecheck(env, code) + val costingRes = getCostingResult(env, internalProp) + val calcF = costingRes.calcF + val tree = IR.buildTree(calcF) + checkSerializationRoundTrip(tree) + tree + } + + // The following is done as part of Interpreter.verify() + val valueFun = { + val costingRes = getCostingResult(env, compiledTree) + val calcF = costingRes.calcF + val tree = IR.buildTree(calcF) + + // sanity check that buildTree is reverse to buildGraph (see doCostingEx) + tree shouldBe compiledTree + + val lA = Liftables.asLiftable[SContext, IR.Context](calcF.elem.eDom.liftable) + val lB = Liftables.asLiftable[Any, Any](calcF.elem.eRange.liftable) + IR.compile[SContext, Any, IR.Context, Any](IR.getDataEnv, calcF)(lA, lB) + } + + val f = (in: A) => { implicit val cA = tA.classTag val x = fromPrimView(in) - val context = - ErgoLikeContextTesting.dummy(createBox(0, TrueProp)) - .withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA)).withBindings(bindings: _*) - val calcCtx = context.toSigmaContext(IR, isCost = false) - val (res, _) = valueFun(calcCtx) + val sigmaCtx = in match { + case ctx: CostingDataContext => + // the context is passed as function argument (this is for testing only) + // This is to overcome non-functional semantics of context operations + // (such as Inputs, Height, etc which don't have arguments and refer to the + // context implicitly). + // These context operations are introduced by buildTree frontend function + // (ctx.HEIGHT method call compiled to Height IR node) + // ------- + // We add ctx as it's own variable with id = 1 + val ctxVar = Extensions.toAnyValue[special.sigma.Context](ctx)(special.sigma.ContextRType) + val newVars = if (ctx.vars.length < 2) { + val vars = ctx.vars.toArray + val buf = new Array[special.sigma.AnyValue](2) + Array.copy(vars, 0, buf, 0, vars.length) + buf(1) = ctxVar + CostingSigmaDslBuilder.Colls.fromArray(buf) + } else { + ctx.vars.updated(1, ctxVar) + } + ctx.copy(vars = newVars) + case _ => + val ergoCtx = ErgoLikeContextTesting.dummy(createBox(0, TrueProp)) + .withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA)) + .withBindings(bindings: _*) + ergoCtx.toSigmaContext(IR, isCost = false) + } + val (res, _) = valueFun(sigmaCtx) res.asInstanceOf[B] } + val Terms.Apply(funcVal, _) = compiledTree.asInstanceOf[SValue] + CompiledFunc(funcScript, bindings.toSeq, funcVal, f) } - def assertExceptionThrown(fun: => Any, assertion: Throwable => Boolean): Unit = { + def assertExceptionThrown(fun: => Any, assertion: Throwable => Boolean, clue: => String = ""): Unit = { try { fun fail("exception is expected") @@ -179,7 +246,11 @@ trait SigmaTestingCommons extends PropSpec catch { case e: Throwable => if (!assertion(e)) - fail(s"exception check failed on $e (root cause: ${rootCause(e)}) \n trace:\n${e.getStackTrace.mkString("\n")}}") + fail( + s"""exception check failed on $e (root cause: ${rootCause(e)}) + |clue: $clue + |trace: + |${e.getStackTrace.mkString("\n")}}""".stripMargin) } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala index 91a48d98e3..3c09275a1b 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala @@ -1,7 +1,13 @@ package sigmastate.serialization +import scorex.crypto.authds.{ADKey, ADValue} +import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert} +import scorex.crypto.hash.{Blake2b256, Digest32} import sigmastate.Values.AvlTreeConstant import sigmastate.AvlTreeFlags +import sigmastate._ +import sigmastate.eval.Extensions._ +import sigmastate.eval._ class AvlTreeSpecification extends SerializationSpecification { @@ -28,4 +34,30 @@ class AvlTreeSpecification extends SerializationSpecification { roundTripTest(v) } } + + property("insert") { + val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) + + val digest = avlProver.digest + val flags = AvlTreeFlags(true, false, false) + val treeData = new AvlTreeData(digest, flags, 32, None) + val tree = SigmaDsl.avlTree(treeData) + + val k = Blake2b256.hash("1") + val v = k + avlProver.performOneOperation(Insert(ADKey @@ k, ADValue @@ v)) + val proof = avlProver.generateProof() + + val resTree = tree.insert(Array(k.toColl -> v.toColl).toColl, proof.toColl).get + val resTreeAgain = tree.insert(Array(k.toColl -> v.toColl).toColl, proof.toColl).get + + // It is possible to insert wrong key. However, verifier is getting different digest then, + // and this is to be checked independently. + val wrongKey = Blake2b256.hash("2") + val resTreeWrong = tree.insert(Array(wrongKey.toColl -> v.toColl).toColl, proof.toColl).get + + resTree shouldBe resTreeAgain + resTree should not be resTreeWrong + } + } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index 2fdcb553f6..c8f716aa1b 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -68,7 +68,7 @@ class SigSerializerSpecification extends SigmaTestingCommons with ObjectGenerato } property("SigSerializer round trip") { - forAll { sb: SigmaBoolean => + forAll(configParams = MinSuccessful(100)) { sb: SigmaBoolean => val expr = sb.toSigmaProp val challenge = Array.fill(32)(Random.nextInt(100).toByte) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 6407db9f64..63c6435612 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -2,26 +2,27 @@ package sigmastate.serialization.generators import org.ergoplatform.ErgoBox._ import org.ergoplatform.SigmaConstants.MaxPropositionBytes -import org.ergoplatform.ErgoScriptPredef.{FalseProp, TrueProp} import org.ergoplatform.validation._ import org.ergoplatform._ -import org.scalacheck.Arbitrary.{arbAnyVal, arbBool, arbByte, arbInt, arbLong, arbOption, arbShort, arbString, arbUnit, arbitrary} -import org.scalacheck.Gen.frequency +import org.scalacheck.Arbitrary._ +import org.scalacheck.Gen.{choose, frequency} +import org.scalacheck.util.Buildable import org.scalacheck.{Arbitrary, Gen} +import scalan.RType import scorex.crypto.authds.{ADDigest, ADKey} import scorex.crypto.hash.Digest32 -import scorex.util.encode.{Base58, Base64} -import scorex.util.{ModifierId, bytesToId} -import sigmastate.Values.{AvlTreeConstant, BigIntConstant, BlockValue, BoxConstant, ByteConstant, CollectionConstant, ConstantPlaceholder, ErgoTree, EvaluatedValue, FalseLeaf, FuncValue, GetVarInt, GroupElementConstant, IntConstant, LongConstant, ShortConstant, SigmaBoolean, SigmaPropConstant, SigmaPropValue, StringConstant, TaggedAvlTree, TaggedBox, TaggedInt, TaggedLong, TaggedVariable, TrueLeaf, Tuple, ValDef, ValUse, Value} +import scorex.util.encode.{Base64, Base58} +import scorex.util.{bytesToId, ModifierId} +import sigmastate.Values._ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.eval.Extensions._ import sigmastate.eval.{CostingBox, SigmaDsl, _} import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.{ContextExtension, CryptoConstants, ProverResult} -import sigmastate.lang.TransformingSigmaBuilder.{mkAppend, mkAtLeast, mkBoolToSigmaProp, mkByteArrayToBigInt, mkByteArrayToLong, mkCollectionConstant, mkConstant, mkDeserializeContext, mkDeserializeRegister, mkDivide, mkDowncast, mkEQ, mkExists, mkExtractAmount, mkExtractBytes, mkExtractBytesWithNoRef, mkExtractCreationInfo, mkExtractId, mkExtractScriptBytes, mkFilter, mkFold, mkForAll, mkGE, mkGT, mkLE, mkLT, mkMapCollection, mkMax, mkMin, mkMinus, mkModulo, mkMultiply, mkNEQ, mkPlus, mkSigmaAnd, mkSigmaOr, mkSizeOf, mkSlice, mkTaggedVariable, mkTuple} +import sigmastate.interpreter.{ProverResult, ContextExtension, CryptoConstants} +import sigmastate.lang.TransformingSigmaBuilder._ import sigmastate._ -import sigmastate.utxo.{Append, ByIndex, DeserializeContext, DeserializeRegister, Exists, ExtractAmount, ExtractBytes, ExtractBytesWithNoRef, ExtractCreationInfo, ExtractId, ExtractRegisterAs, ExtractScriptBytes, Filter, Fold, ForAll, GetVar, MapCollection, OptionGet, OptionGetOrElse, OptionIsDefined, SizeOf, Slice, Transformer} +import sigmastate.utxo._ import special.collection.Coll import special.sigma._ @@ -259,6 +260,11 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with bytes <- Gen.listOfN(length, arbitrary[T]) } yield bytes.toArray + implicit def collGen[T: Arbitrary : RType]: Gen[Coll[T]] = { + implicit val cT = RType[T].classTag + arrayGen[T].map(Colls.fromArray[T](_)) + } + def wrappedTypeGen[T <: SType](tpe: T): Gen[T#WrappedType] = (tpe match { case SBoolean => arbBool case SByte => arbByte @@ -300,10 +306,12 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with lazy val modifierIdBytesGen: Gen[Coll[Byte]] = Gen.listOfN(32, arbByte.arbitrary) .map(id => bytesToId(id.toArray).toBytes.toColl) + val MaxTokens = 10 + val ergoBoxGen: Gen[ErgoBox] = for { tId <- modifierIdGen boxId <- unsignedShortGen - tokensCount <- Gen.chooseNum[Int](0, 20) + tokensCount <- Gen.chooseNum[Int](0, MaxTokens) tokens <- Gen.sequence(additionalTokensGen(tokensCount)).map(_.asScala.map(_._1)) candidate <- ergoBoxCandidateGen(tokens) } yield candidate.toBox(tId, boxId) @@ -313,17 +321,24 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with regs <- Gen.sequence(additionalRegistersGen(regNum)) } yield regs.asScala.toMap + def arrayOfN[T](n: Int, g: Gen[T])(implicit evb: Buildable[T,Array[T]]): Gen[Array[T]] = { + Gen.containerOfN[Array, T](n, g) + } + def ergoBoxCandidateGen(availableTokens: Seq[Digest32]): Gen[ErgoBoxCandidate] = for { l <- arbLong.arbitrary b <- ergoTreeGen.filter(t => t.bytes.length < MaxPropositionBytes.value) ar <- additionalRegistersGen - tokensCount <- Gen.chooseNum[Int](0, 20) - tokens <- if(availableTokens.nonEmpty) { - Gen.listOfN(tokensCount, Gen.oneOf(availableTokens)) - } else { - Gen.oneOf(Seq(List[Digest32]())) - } - tokenAmounts <- Gen.listOfN(tokensCount, Gen.oneOf(1, 500, 20000, 10000000, Long.MaxValue)) + tokens <- + if(availableTokens.nonEmpty) { + for { + tokensCount <- Gen.chooseNum[Int](0, MaxTokens) + ts <- arrayOfN(tokensCount, Gen.oneOf(availableTokens).map(_.toColl)) + } yield ts.distinct.map(coll => Digest32 @@ coll.toArray) + } else { + Gen.oneOf(Seq(Array[Digest32]())) + } + tokenAmounts <- arrayOfN(tokens.length, Gen.oneOf(1, 500, 20000, 10000000, Long.MaxValue)) creationHeight <- heightGen } yield new ErgoBoxCandidate(l, b, creationHeight, tokens.toColl.zip(tokenAmounts.toColl), ar) @@ -336,8 +351,8 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with val tokenIdGen: Gen[Digest32] = digest32Gen val tokensGen: Gen[Seq[Digest32]] = for { - count <- Gen.chooseNum(10, 50) - tokens <- Gen.listOfN(count, tokenIdGen) + count <- Gen.chooseNum(1, MaxTokens) + tokens <- arrayOfN(count, tokenIdGen) } yield tokens val digest32CollGen: Gen[Digest32Coll] = digest32Gen.map(Digest32Coll @@ _.toColl) @@ -655,12 +670,6 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with ErgoTree.withoutSegregation)) } yield treeBuilder(prop) - val headerGen: Gen[Header] = for { - stateRoot <- avlTreeGen - parentId <- modifierIdBytesGen - header <- headerGen(stateRoot, parentId) - } yield header - def headerGen(stateRoot: AvlTree, parentId: Coll[Byte]): Gen[Header] = for { id <- modifierIdBytesGen version <- arbByte.arbitrary @@ -678,19 +687,23 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, height, extensionRoot, minerPk, powOnetimePk, powNonce, powDistance, votes) + val headerGen: Gen[Header] = for { + stateRoot <- avlTreeGen + parentId <- modifierIdBytesGen + header <- headerGen(stateRoot, parentId) + } yield header + + implicit val arbHeader = Arbitrary(headerGen) + + val MaxHeaders = 2 def headersGen(stateRoot: AvlTree): Gen[Seq[Header]] = for { - size <- Gen.chooseNum(0, 10) + size <- Gen.chooseNum(0, MaxHeaders) } yield if (size == 0) Seq() else (0 to size) .foldLeft(List[Header](headerGen(stateRoot, modifierIdBytesGen.sample.get).sample.get)) { (h, _) => h :+ headerGen(stateRoot, h.last.id).sample.get }.reverse - val preHeaderGen: Gen[PreHeader] = for { - parentId <- modifierIdBytesGen - preHeader <- preHeaderGen(parentId) - } yield preHeader - def preHeaderGen(parentId: Coll[Byte]): Gen[PreHeader] = for { version <- arbByte.arbitrary timestamp <- arbLong.arbitrary @@ -700,6 +713,13 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with votes <- minerVotesGen } yield CPreHeader(version, parentId, timestamp, nBits, height, minerPk, votes) + val preHeaderGen: Gen[PreHeader] = for { + parentId <- modifierIdBytesGen + preHeader <- preHeaderGen(parentId) + } yield preHeader + + implicit val arbPreHeader = Arbitrary(preHeaderGen) + val ergoLikeTransactionGen: Gen[ErgoLikeTransaction] = for { inputBoxesIds <- Gen.nonEmptyListOf(boxIdGen) dataInputBoxIds <- Gen.listOf(boxIdGen) @@ -708,9 +728,9 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with def ergoLikeTransactionGen(inputBoxesIds: Seq[BoxId], dataInputBoxIds: Seq[BoxId]): Gen[ErgoLikeTransaction] = for { tokens <- tokensGen - outputsCount <- Gen.chooseNum(50, 200) - outputCandidates <- Gen.listOfN(outputsCount, ergoBoxCandidateGen(tokens)) - proofs <- Gen.listOfN(inputBoxesIds.length, serializedProverResultGen) + outputsCount <- Gen.chooseNum(1, MaxOutputBoxes) + outputCandidates <- arrayOfN(outputsCount, ergoBoxCandidateGen(tokens)) + proofs <- arrayOfN(inputBoxesIds.length, serializedProverResultGen) } yield new ErgoLikeTransaction( inputs = inputBoxesIds.zip(proofs).map(t => Input(t._1, t._2)).toIndexedSeq, dataInputs = dataInputBoxIds.map(DataInput).toIndexedSeq, @@ -725,9 +745,9 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with def unsignedErgoLikeTransactionGen(inputBoxesIds: Seq[BoxId], dataInputBoxIds: Seq[BoxId]): Gen[UnsignedErgoLikeTransaction] = for { tokens <- tokensGen - outputsCount <- Gen.chooseNum(50, 200) - outputCandidates <- Gen.listOfN(outputsCount, ergoBoxCandidateGen(tokens)) - contextExtensions <- Gen.listOfN(inputBoxesIds.length, contextExtensionGen) + outputsCount <- Gen.chooseNum(1, MaxOutputBoxes) + outputCandidates <- arrayOfN(outputsCount, ergoBoxCandidateGen(tokens)) + contextExtensions <- arrayOfN(inputBoxesIds.length, contextExtensionGen) } yield new UnsignedErgoLikeTransaction( inputs = inputBoxesIds.zip(contextExtensions).map(t => new UnsignedInput(t._1, t._2)).toIndexedSeq, dataInputs = dataInputBoxIds.map(DataInput).toIndexedSeq, @@ -737,12 +757,16 @@ trait ObjectGenerators extends TypeGenerators with ValidationSpecification with val ergoLikeTransactionTemplateGen: Gen[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = Gen.oneOf(unsignedErgoLikeTransactionGen, ergoLikeTransactionGen) + val MaxDataBoxes = 5 + val MaxInputBoxes = 5 + val MaxOutputBoxes = 100 + val ergoLikeContextGen: Gen[ErgoLikeContext] = for { stateRoot <- avlTreeGen headers <- headersGen(stateRoot) preHeader <- preHeaderGen(headers.headOption.map(_.id).getOrElse(modifierIdBytesGen.sample.get)) - dataBoxes <- Gen.listOf(ergoBoxGen) - boxesToSpend <- Gen.nonEmptyListOf(ergoBoxGen) + dataBoxes <- choose(0, MaxDataBoxes).flatMap(arrayOfN(_, ergoBoxGen)) + boxesToSpend <- choose(1, MaxInputBoxes).flatMap(arrayOfN(_, ergoBoxGen)).suchThat(_.size > 0) extension <- contextExtensionGen costLimit <- arbLong.arbitrary initCost <- arbLong.arbitrary diff --git a/sigmastate/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala b/sigmastate/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala index ceb58bd3fe..8521703809 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala @@ -30,7 +30,7 @@ trait TypeGenerators { Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree) implicit val arbPredefType = Arbitrary(predefTypeGen) - implicit def getToArbitrary[T: Gen]: Arbitrary[T] = Arbitrary(implicitly[Gen[T]]) + implicit def genToArbitrary[T: Gen]: Arbitrary[T] = Arbitrary(implicitly[Gen[T]]) def sTupleGen(min: Int, max: Int): Gen[STuple] = for { length <- Gen.chooseNum(min, max) diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala index e11bcb6969..2a88e05826 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala @@ -1,6 +1,6 @@ package sigmastate.utxo -import org.ergoplatform.{ErgoLikeContext, ErgoLikeTransaction, Height} +import org.ergoplatform.Height import org.scalacheck.Gen import sigmastate.Values.IntConstant import sigmastate._ @@ -10,7 +10,8 @@ import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeConte import scala.util.Random class ComplexSigSpecification extends SigmaTestingCommons { - implicit lazy val IR = new TestingIRContext + implicit lazy val IR: TestingIRContext = new TestingIRContext + private def proverGen: Gen[ContextEnrichingTestProvingInterpreter] = for { _ <- Gen.const(1) } yield new ContextEnrichingTestProvingInterpreter() @@ -612,4 +613,44 @@ class ComplexSigSpecification extends SigmaTestingCommons { } } } + + property("nested thresholds") { + val prover = new ContextEnrichingTestProvingInterpreter + val verifier = new ErgoLikeTestInterpreter + + val secret1 = prover.dlogSecrets.head + val secret2 = prover.dlogSecrets(1) + val secret3 = prover.dlogSecrets(2) + val secret4 = prover.dlogSecrets(3) + + val pdlog1 = secret1.publicImage + val pdlog2 = secret2.publicImage + val pdlog3 = secret3.publicImage + val pdlog4 = secret4.publicImage + + val otherProver = new ContextEnrichingTestProvingInterpreter + + val unknownSecret1 = otherProver.dlogSecrets.head + val unknownSecret2 = otherProver.dlogSecrets(1) + val unknownSecret3 = otherProver.dlogSecrets(2) + + val unknownPdlog1 = unknownSecret1.publicImage + val unknownPdlog2 = unknownSecret2.publicImage + val unknownPdlog3 = unknownSecret3.publicImage + + val c1 = CTHRESHOLD(2, Seq(pdlog1, pdlog2, unknownPdlog1)) + val c2 = CTHRESHOLD(2, Seq(pdlog3, pdlog4, unknownPdlog2)) + val c3 = CTHRESHOLD(2, Seq(unknownPdlog1, unknownPdlog2, unknownPdlog3)) + + val prop = CTHRESHOLD(2, Seq(c1, c2, c3)) + + val ctx = fakeContext + + val pr = prover.prove(prop, ctx, fakeMessage).get + + otherProver.prove(prop, ctx, fakeMessage).isFailure shouldBe true + + verifier.verify(prop, ctx, pr, fakeMessage).isSuccess shouldBe true + } + } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala new file mode 100644 index 0000000000..8374057d86 --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala @@ -0,0 +1,415 @@ +package sigmastate.utxo + +import sigmastate._ +import sigmastate.basics.DLogProtocol.DLogInteractiveProver +import sigmastate.basics.DiffieHellmanTupleInteractiveProver +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.interpreter._ +import sigmastate.lang.Terms._ + +class DistributedSigSpecification extends SigmaTestingCommons { + + implicit lazy val IR: TestingIRContext = new TestingIRContext + + private val ctx = fakeContext + + /** + * An example test where Alice (A) and Bob (B) are signing an input in a distributed way. A statement which + * protects the box to spend is "pubkey_Alice && pubkey_Bob". Note that a signature in this case is about + * a transcript of a Sigma-protocol ((a_Alice, a_Bob), e, (z_Alice, z_Bob)), + * which is done in non-interactive way (thus "e" is got via a Fiat-Shamir transformation). + * + * For that, they are going through following steps: + * + * - Bob is generating first protocol message a_Bob and sends it to Alice + * - Alice forms a hint which contain Bob's commitment "a_Bob", and puts the hint into a hints bag + * - She proves the statement using the bag, getting the partial protocol transcript + * (a_Alice, e, z_Alice) as a result and sends "a_Alice" and "z_Alice" to Bob. + * Please note that "e" is got from both a_Alice and a_Bob. + * + * - Bob now also knows a_Alice, so can generate the same "e" as Alice. Thus Bob is generating valid + * proof ((a_Alice, a_Bob), e, (z_Alice, z_Bob)). + */ + property("distributed AND (2 out of 2)") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val verifier: ContextEnrichingTestProvingInterpreter = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob) + val prop: Values.Value[SSigmaProp.type] = compile(env, """pubkeyA && pubkeyB""").asSigmaProp + + val (rBob, aBob) = DLogInteractiveProver.firstMessage() + + val hintFromBob: Hint = RealCommitment(pubkeyBob, aBob) + val bagA = HintsBag(Seq(hintFromBob)) + + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + val bagB = proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice)) + .addHint(OwnCommitment(pubkeyBob, rBob, aBob)) + + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + // Compound proof from Bob is correct + verifier.verify(prop, ctx, proofBob, fakeMessage).get._1 shouldBe true + } + + /** + * 3-out-of-3 AND signature + */ + property("distributed AND (3 out of 3)") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val verifier: ContextEnrichingTestProvingInterpreter = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + val pubkeyCarol = proverC.dlogSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol) + val prop: Values.Value[SSigmaProp.type] = compile(env, """pubkeyA && pubkeyB && pubkeyC""").asSigmaProp + + val (rBob, aBob) = DLogInteractiveProver.firstMessage() + val (rCarol, aCarol) = DLogInteractiveProver.firstMessage() + + val dlBKnown: Hint = RealCommitment(pubkeyBob, aBob) + val dlCKnown: Hint = RealCommitment(pubkeyCarol, aCarol) + val bagA = HintsBag(Seq(dlBKnown, dlCKnown)) + + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + val bagC = proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice)) + .addHint(OwnCommitment(pubkeyCarol, rCarol, aCarol)) + .addHint(dlBKnown) + + val proofCarol = proverC.prove(prop, ctx, fakeMessage, bagC).get + + val bagB = proverB.bagForMultisig(ctx, prop, proofCarol.proof, Seq(pubkeyAlice, pubkeyCarol)) + .addHint(OwnCommitment(pubkeyBob, rBob, aBob)) + + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofCarol, fakeMessage).get._1 shouldBe false + + // Compound proof from Bob is correct + verifier.verify(prop, ctx, proofBob, fakeMessage).get._1 shouldBe true + } + + + /** + * An example test where Alice (A), Bob (B) and Carol (C) are signing in a distributed way an input, which is + * protected by 2-out-of-3 threshold multi-signature. + * + * A statement which protects the box to spend is "atLeast(2, Coll(pubkeyA, pubkeyB, pubkeyC))". + * + * A scheme for multisigning is following: + * + * - Bob is generating first protocol message (commitment to his randomness) "a" and sends it to Alice + * - Alice is generating her proof having Bob's "a" as a hint. She then puts Bob's randomness and fill his + * response with zero bits. Thus Alice's signature is not valid. Alice is sending her signature to Bob. + * - Bob is extracting Alice's commitment to randomness and response, and also Carol's commitment and response. + * He's using his randomness from his first step and completes the (valid) signature. + */ + property("distributed THRESHOLD - 2 out of 3") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + val pubkeyCarol = proverC.dlogSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol) + val prop = compile(env, """atLeast(2, Coll(pubkeyA, pubkeyB, pubkeyC))""").asSigmaProp + + val (rBob, aBob) = DLogInteractiveProver.firstMessage() + val dlBKnown: Hint = RealCommitment(pubkeyBob, aBob) + + val bagA = HintsBag(Seq(dlBKnown)) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + val bagB = proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyCarol)) + .addHint(OwnCommitment(pubkeyBob, rBob, aBob)) + + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + // Compound proof from Bob is correct + verifier.verify(prop, ctx, proofBob, fakeMessage).get._1 shouldBe true + } + + /** + * Distributed threshold signature, 3 out of 4 case. + */ + property("distributed THRESHOLD - 3 out of 4") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val proverD = new ErgoLikeTestProvingInterpreter + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + val pubkeyCarol = proverC.dlogSecrets.head.publicImage + val pubkeyDave = proverD.dlogSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol, "pubkeyD" -> pubkeyDave) + val prop = compile(env, """atLeast(3, Coll(pubkeyA, pubkeyB, pubkeyC, pubkeyD))""").asSigmaProp + + //Alice, Bob and Carol are signing + val (rBob, aBob) = DLogInteractiveProver.firstMessage() + val dlBKnown: Hint = RealCommitment(pubkeyBob, aBob) + + val (rCarol, aCarol) = DLogInteractiveProver.firstMessage() + val dlCKnown: Hint = RealCommitment(pubkeyCarol, aCarol) + + val bagA = HintsBag(Seq(dlBKnown, dlCKnown)) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + val bagC = proverC.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyDave)) ++ + HintsBag(Seq(dlBKnown, OwnCommitment(pubkeyCarol, rCarol, aCarol))) + val proofCarol = proverC.prove(prop, ctx, fakeMessage, bagC).get + + val bagB = (proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyDave)) ++ + proverB.bagForMultisig(ctx, prop, proofCarol.proof, Seq(pubkeyCarol))) + .addHint(OwnCommitment(pubkeyBob, rBob, aBob)) + + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofCarol, fakeMessage).get._1 shouldBe false + + // Compound proof from Bob is correct + verifier.verify(prop, ctx, proofBob, fakeMessage).get._1 shouldBe true + } + + /** + * Distributed threshold signature, 3 out of 4 case, 1 real and 1 simulated secrets are of DH kind. + */ + property("distributed THRESHOLD - 3 out of 4 - w. DH") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val proverD = new ErgoLikeTestProvingInterpreter + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dhSecrets.head.publicImage + val pubkeyCarol = proverC.dhSecrets.head.publicImage + val pubkeyDave = proverD.dhSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol, "pubkeyD" -> pubkeyDave) + val prop = compile(env, """atLeast(3, Coll(pubkeyA, pubkeyB, pubkeyC, pubkeyD))""").asSigmaProp + + // Alice, Bob and Carol are signing + val (rBob, aBob) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyBob) + val dlBKnown: Hint = RealCommitment(pubkeyBob, aBob) + + val (rCarol, aCarol) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyCarol) + val dlCKnown: Hint = RealCommitment(pubkeyCarol, aCarol) + + val bagA = HintsBag(Seq(dlBKnown, dlCKnown)) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + val bagC = proverC.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyDave)) ++ + HintsBag(Seq(dlBKnown, OwnCommitment(pubkeyCarol, rCarol, aCarol))) + val proofCarol = proverC.prove(prop, ctx, fakeMessage, bagC).get + + val bagB = (proverB.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyDave)) ++ + proverB.bagForMultisig(ctx, prop, proofCarol.proof, Seq(pubkeyCarol))) + .addHint(OwnCommitment(pubkeyBob, rBob, aBob)) + + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + + // Proof generated by Alice without getting Bob's part is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + // Compound proof from Bob is correct + verifier.verify(prop, ctx, proofBob, fakeMessage).get._1 shouldBe true + } + + property("distributed THRESHOLD - 2 out of 5") { + + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val proverD = new ErgoLikeTestProvingInterpreter + val proverE = new ErgoLikeTestProvingInterpreter + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + val pubkeyCarol = proverC.dlogSecrets.head.publicImage + val pubkeyDave = proverD.dlogSecrets.head.publicImage + val pubkeyEmma = proverE.dlogSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol, + "pubkeyD" -> pubkeyDave, "pubkeyE" -> pubkeyEmma) + val prop = compile(env, """atLeast(2, Coll(pubkeyA, pubkeyB, pubkeyC, pubkeyD, pubkeyE))""").asSigmaProp + + //Alice and Dave are signing + val (rDave, aDave) = DLogInteractiveProver.firstMessage() + val dlDKnown: Hint = RealCommitment(pubkeyDave, aDave) + + val bagA = HintsBag(Seq(dlDKnown)) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + + // Proof generated by Alice without interaction w. Dave is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + val bagD = proverD + .bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq(pubkeyBob, pubkeyCarol, pubkeyEmma)) + .addHint(OwnCommitment(pubkeyDave, rDave, aDave)) + + val proofDave = proverD.prove(prop, ctx, fakeMessage, bagD).get + verifier.verify(prop, ctx, proofDave, fakeMessage).get._1 shouldBe true + } + + property("distributed THRESHOLD - 4 out of 8 - DH") { + + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val proverD = new ErgoLikeTestProvingInterpreter + val proverE = new ErgoLikeTestProvingInterpreter + val proverF = new ErgoLikeTestProvingInterpreter + val proverG = new ErgoLikeTestProvingInterpreter + val proverH = new ErgoLikeTestProvingInterpreter + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dhSecrets.head.publicImage + val pubkeyBob = proverB.dhSecrets.head.publicImage + val pubkeyCarol = proverC.dhSecrets.head.publicImage + val pubkeyDave = proverD.dhSecrets.head.publicImage + val pubkeyEmma = proverE.dhSecrets.head.publicImage + val pubkeyFrank = proverF.dhSecrets.head.publicImage + val pubkeyGerard = proverG.dhSecrets.head.publicImage + val pubkeyHannah = proverH.dhSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol, + "pubkeyD" -> pubkeyDave, "pubkeyE" -> pubkeyEmma, "pubkeyF" -> pubkeyFrank, + "pubkeyG" -> pubkeyGerard, "pubkeyH" -> pubkeyHannah) + val script = """atLeast(4, Coll(pubkeyA, pubkeyB, pubkeyC, pubkeyD, pubkeyE, pubkeyF, pubkeyG, pubkeyH))""" + val prop = compile(env, script).asSigmaProp + + //Alice, Bob, Gerard, and Hannah are signing, others are simulated + + //first, commitments are needed from real signers + val (rAlice, aAlice) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyAlice) + val dlAKnown: Hint = RealCommitment(pubkeyAlice, aAlice) + val secretCmtA = OwnCommitment(pubkeyAlice, rAlice, aAlice) + + val (rBob, aBob) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyBob) + val dlBKnown: Hint = RealCommitment(pubkeyBob, aBob) + val secretCmtB = OwnCommitment(pubkeyBob, rBob, aBob) + + val (rGerard, aGerard) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyGerard) + val dlGKnown: Hint = RealCommitment(pubkeyGerard, aGerard) + val secretCmtG = OwnCommitment(pubkeyGerard, rGerard, aGerard) + + val (rHannah, aHannah) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyHannah) + val secretCmtH = OwnCommitment(pubkeyHannah, rHannah, aHannah) + + val bagH = HintsBag(Seq(dlAKnown, dlBKnown, dlGKnown, secretCmtH)) + val proofHannah = proverH.prove(prop, ctx, fakeMessage, bagH).get + + // Proof generated by Hannah only is not correct + verifier.verify(prop, ctx, proofHannah, fakeMessage).get._1 shouldBe false + + //hints after the first real proof done. + val bag1 = proverH + .bagForMultisig(ctx, prop, proofHannah.proof, + Seq(pubkeyHannah), + Seq(pubkeyCarol, pubkeyDave, pubkeyEmma, pubkeyFrank)) + + //now real proofs can be done in any order + val bagB = bag1.addHints(OwnCommitment(pubkeyBob, rBob, aBob), dlAKnown, dlGKnown) + val proofBob = proverB.prove(prop, ctx, fakeMessage, bagB).get + val partialBobProofBag = proverB.bagForMultisig(ctx, prop, proofBob.proof, Seq(pubkeyBob), Seq.empty).realProofs.head + + val bagA = bag1.addHints(OwnCommitment(pubkeyAlice, rAlice, aAlice), dlBKnown, dlGKnown) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + val partialAliceProofBag = proverA.bagForMultisig(ctx, prop, proofAlice.proof, Seq(pubkeyAlice), Seq.empty).realProofs.head + + val bagG = bag1.addHints(OwnCommitment(pubkeyGerard, rGerard, aGerard), dlAKnown, dlBKnown) + val proofGerard = proverG.prove(prop, ctx, fakeMessage, bagG).get + val partialGerardProofBag = proverG.bagForMultisig(ctx, prop, proofGerard.proof, Seq(pubkeyGerard), Seq.empty).realProofs.head + + val bag = bag1 + .addHints(partialAliceProofBag, partialBobProofBag, partialGerardProofBag) + .addHints(dlAKnown, dlBKnown, dlGKnown) + + val validProofA = proverA.prove(prop, ctx, fakeMessage, bag.addHint(secretCmtA)).get + verifier.verify(prop, ctx, validProofA, fakeMessage).get._1 shouldBe true + + val validProofB = proverB.prove(prop, ctx, fakeMessage, bag.addHint(secretCmtB)).get + verifier.verify(prop, ctx, validProofB, fakeMessage).get._1 shouldBe true + + val validProofG = proverG.prove(prop, ctx, fakeMessage, bag.addHint(secretCmtG)).get + verifier.verify(prop, ctx, validProofG, fakeMessage).get._1 shouldBe true + + val validProofH = proverH.prove(prop, ctx, fakeMessage, bag.addHint(secretCmtH)).get + verifier.verify(prop, ctx, validProofH, fakeMessage).get._1 shouldBe true + + validProofA.proof.sameElements(validProofB.proof) shouldBe true + validProofB.proof.sameElements(validProofG.proof) shouldBe true + validProofG.proof.sameElements(validProofH.proof) shouldBe true + } + + property("distributed THRESHOLD - (1-out-of-2) and (1-out-of-2) - DLOG and DH") { + val proverA = new ErgoLikeTestProvingInterpreter + val proverB = new ErgoLikeTestProvingInterpreter + val proverC = new ErgoLikeTestProvingInterpreter + val proverD = new ErgoLikeTestProvingInterpreter + + val verifier = new ContextEnrichingTestProvingInterpreter + + val pubkeyAlice = proverA.dlogSecrets.head.publicImage + val pubkeyBob = proverB.dlogSecrets.head.publicImage + val pubkeyCarol = proverC.dhSecrets.head.publicImage + val pubkeyDave = proverD.dhSecrets.head.publicImage + + val env = Map("pubkeyA" -> pubkeyAlice, "pubkeyB" -> pubkeyBob, "pubkeyC" -> pubkeyCarol, "pubkeyD" -> pubkeyDave) + val script = """(pubkeyA || pubkeyB) && (pubkeyC || pubkeyD)""" + val prop = compile(env, script).asSigmaProp + + //Alice and Dave are signing + + //first, commitments are needed from real signers + val (rAlice, aAlice) = DLogInteractiveProver.firstMessage() + val secretCmtA = OwnCommitment(pubkeyAlice, rAlice, aAlice) + + val (rDave, aDave) = DiffieHellmanTupleInteractiveProver.firstMessage(pubkeyDave) + val dlDKnown: Hint = RealCommitment(pubkeyDave, aDave) + val secretCmtD = OwnCommitment(pubkeyDave, rDave, aDave) + + val bagA = HintsBag(Seq(secretCmtA, dlDKnown)) + val proofAlice = proverA.prove(prop, ctx, fakeMessage, bagA).get + // Proof generated by Alice only is not correct + verifier.verify(prop, ctx, proofAlice, fakeMessage).get._1 shouldBe false + + val bag = proverA.bagForMultisig(ctx, prop, proofAlice.proof, + Seq(pubkeyAlice, pubkeyDave), Seq(pubkeyBob, pubkeyCarol)) + + val validProofD = proverD.prove(prop, ctx, fakeMessage, bag.addHint(secretCmtD)).get + verifier.verify(prop, ctx, validProofD, fakeMessage).get._1 shouldBe true + } + +} diff --git a/sigmastate/src/test/scala/sigmastate/utxo/benchmarks/CrowdFundingKernelContract.scala b/sigmastate/src/test/scala/sigmastate/utxo/benchmarks/CrowdFundingKernelContract.scala index c15933dd47..8cd0722134 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/benchmarks/CrowdFundingKernelContract.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/benchmarks/CrowdFundingKernelContract.scala @@ -8,7 +8,6 @@ import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, DLogProverInput, F import sigmastate.basics.VerifierMessage.Challenge import scorex.crypto.hash.Blake2b256 import sigmastate._ -import sigmastate.lang.Terms._ import sigmastate.helpers.ContextEnrichingTestProvingInterpreter import sigmastate.interpreter.{CryptoConstants, Interpreter} import sigmastate.utils.Helpers @@ -35,7 +34,7 @@ class CrowdFundingKernelContract( assert(su.challengeOpt.isDefined) DLogInteractiveProver.simulate(su.proposition,su.challengeOpt.get).asInstanceOf[UnprovenTree] } else { - val (r, commitment) = DLogInteractiveProver.firstMessage(pubKey) + val (r, commitment) = DLogInteractiveProver.firstMessage() UnprovenSchnorr(pubKey, Some(commitment), Some(r), None, simulated = false) } @@ -77,7 +76,7 @@ class CrowdFundingKernelContract( val a = dlog.multiplyGroupElements( dlog.exponentiate(g, sn.secondMessage.z.underlying()), - dlog.getInverse(dlog.exponentiate(h, new BigInteger(1, sn.challenge)))) + dlog.inverseOf(dlog.exponentiate(h, new BigInteger(1, sn.challenge)))) val rootCommitment = FirstDLogProverMessage(a) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpec.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpec.scala new file mode 100644 index 0000000000..52c36afa65 --- /dev/null +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpec.scala @@ -0,0 +1,4226 @@ +package special.sigma + +import java.math.BigInteger + +import org.ergoplatform.ErgoScriptPredef.TrueProp +import org.ergoplatform._ +import org.ergoplatform.settings.ErgoAlgos +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.prop.{PropertyChecks, TableFor2} +import org.scalatest.{PropSpec, Matchers, Tag} +import scalan.{ExactNumeric, RType} +import scorex.crypto.authds.avltree.batch._ +import scorex.crypto.authds.{ADDigest, ADKey, ADValue} +import scorex.crypto.hash.{Digest32, Blake2b256} +import scalan.util.Extensions._ +import sigma.util.Extensions._ +import sigmastate.SCollection._ +import sigmastate.Values.IntConstant +import sigmastate._ +import sigmastate.basics.DLogProtocol._ +import sigmastate.Values._ +import sigmastate.lang.Terms.Apply +import sigmastate.eval.Extensions._ +import sigmastate.eval._ +import sigmastate.lang.Terms.MethodCall +import sigmastate.utxo._ +import special.collection._ +import sigmastate.serialization.OpCodes.OpCode +import sigmastate.utils.Helpers + +import scala.reflect.ClassTag +import scala.util.{DynamicVariable, Success, Failure, Try} +import OrderingOps._ +import scorex.util.ModifierId +import sigmastate.basics.{ProveDHTuple, DLogProtocol} +import sigmastate.helpers.SigmaPPrint + +import scala.math.Ordering + +/** This suite tests every method of every SigmaDsl type to be equivalent to + * the evaluation of the corresponding ErgoScript operation */ +class SigmaDslSpec extends SigmaDslTesting { suite => + + override implicit val generatorDrivenConfig = PropertyCheckConfiguration(minSuccessful = 30) + + ///===================================================== + /// Boolean type operations + ///----------------------------------------------------- + + property("Boolean methods equivalence") { + val toByte = newFeature((x: Boolean) => x.toByte, "{ (x: Boolean) => x.toByte }") + + val cases = Seq( + (true, Success(1.toByte)), + (false, Success(0.toByte)) + ) + + testCases(cases, toByte) + } + + property("BinXor(logical XOR) equivalence") { + val binXor = existingFeature((x: (Boolean, Boolean)) => x._1 ^ x._2, + "{ (x: (Boolean, Boolean)) => x._1 ^ x._2 }", + FuncValue( + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + BinXor( + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 1.toByte), + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 2.toByte) + ) + )) + val cases = Seq( + ((true, true), Try(false)), + ((true, false), Try(true)), + ((false, false), Try(false)), + ((false, true), Try(true)) + ) + testCases(cases, binXor) + } + + property("BinXor(logical XOR) test") { + val xor = existingFeature((x: (Int, Boolean)) => (x._1 == 0) ^ x._2, + "{ (x: (Int, Boolean)) => (x._1 == 0) ^ x._2 }", + FuncValue( + Vector((1, STuple(Vector(SInt, SBoolean)))), + BinXor( + EQ( + SelectField.typed[IntValue](ValUse(1, STuple(Vector(SInt, SBoolean))), 1.toByte), + IntConstant(0) + ), + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SInt, SBoolean))), 2.toByte) + ) + )) + val cases = Seq( + ((1095564593, true), Success(true)), + ((-901834021, true), Success(true)), + ((595045530, false), Success(false)), + ((-1157998227, false), Success(false)), + ((0, true), Success(false)), + ((0, false), Success(true)) + ) + testCases(cases, xor) + } + + property("&& boolean equivalence") { + lazy val eq = existingFeature((x:(Boolean, Boolean)) => x._1 && x._2, + "{ (x:(Boolean, Boolean)) => x._1 && x._2 }", + FuncValue( + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + BinAnd( + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 1.toByte), + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 2.toByte) + ) + )) + val cases = Seq( + ((false, true), Success(false)), + ((false, false), Success(false)), + ((true, true), Success(true)), + ((true, false), Success(false)) + ) + testCases(cases, eq) + } + + property("|| boolean equivalence") { + lazy val eq = existingFeature((x:(Boolean, Boolean)) => x._1 || x._2, + "{ (x:(Boolean, Boolean)) => x._1 || x._2 }", + FuncValue( + Vector((1, STuple(Vector(SBoolean, SBoolean)))), + BinOr( + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 1.toByte), + SelectField.typed[BoolValue](ValUse(1, STuple(Vector(SBoolean, SBoolean))), 2.toByte) + ) + )) + val cases = Seq( + ((true, false), Success(true)), + ((true, true), Success(true)), + ((false, false), Success(false)), + ((false, true), Success(true)) + ) + testCases(cases, eq) + } + + property("lazy || and && boolean equivalence") { + testCases( + Seq( + (true, Success(true)), + (false, Failure(new ArithmeticException("/ by zero"))) + ), + existingFeature((x: Boolean) => x || (1 / 0 == 1), + "{ (x: Boolean) => x || (1 / 0 == 1) }", + FuncValue( + Vector((1, SBoolean)), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ))) + + testCases( + Seq( + (true, Failure(new ArithmeticException("/ by zero"))), + (false, Success(false)) + ), + existingFeature((x: Boolean) => x && (1 / 0 == 1), + "{ (x: Boolean) => x && (1 / 0 == 1) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ))) + + testCases( + Seq( + (false, Success(false)), + (true, Success(true)) + ), + existingFeature((x: Boolean) => x && (x || (1 / 0 == 1)), + "{ (x: Boolean) => x && (x || (1 / 0 == 1)) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + ValUse(1, SBoolean), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ))) + + testCases( + Seq( + (false, Success(false)), + (true, Success(true)) + ), + existingFeature((x: Boolean) => x && (x && (x || (1 / 0 == 1))), + "{ (x: Boolean) => x && (x && (x || (1 / 0 == 1))) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + ValUse(1, SBoolean), + BinAnd( + ValUse(1, SBoolean), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ) + ))) + + testCases( + Seq( + (false, Success(false)), + (true, Success(true)) + ), + existingFeature((x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))), + "{ (x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + ValUse(1, SBoolean), + BinAnd( + ValUse(1, SBoolean), + BinAnd( + ValUse(1, SBoolean), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ) + ) + ))) + + testCases( + Seq( + (false, Failure(new ArithmeticException("/ by zero"))), + (true, Success(true)) + ), + existingFeature((x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)), + "{ (x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + LogicalNot( + BinAnd( + LogicalNot(ValUse(1, SBoolean)), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ))) + + testCases( + Seq( + (true, Success(true)), + (false, Failure(new ArithmeticException("/ by zero"))) + ), + existingFeature((x: Boolean) => (x || (1 / 0 == 1)) && x, + "{ (x: Boolean) => (x || (1 / 0 == 1)) && x }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ), + ValUse(1, SBoolean) + ) + ))) + + testCases( + Seq( + (true, Success(true)), + (false, Failure(new ArithmeticException("/ by zero"))) + ), + existingFeature((x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)), + "{ (x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ))) + + testCases( + Seq( + (true, Success(true)), + (false, Failure(new ArithmeticException("/ by zero"))) + ), + existingFeature( + (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (x || (1 / 0 == 1)), + "{ (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (x || (1 / 0 == 1)) }", + FuncValue( + Vector((1, SBoolean)), + BinAnd( + BinOr( + LogicalNot( + BinAnd( + LogicalNot(ValUse(1, SBoolean)), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(0)) + ), + BinOr( + ValUse(1, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ))) + + testCases( + Seq( + (false, Failure(new ArithmeticException("/ by zero"))), + (true, Success(true)) + ), + existingFeature( + (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (!(!x && (1 / 0 == 1)) || (1 / 0 == 1)), + "{ (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (!(!x && (1 / 0 == 1)) || (1 / 0 == 1)) }", + FuncValue( + Vector((1, SBoolean)), + BlockValue( + Vector(ValDef(3, List(), LogicalNot(ValUse(1, SBoolean)))), + BinAnd( + BinOr( + LogicalNot( + BinAnd( + ValUse(3, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(0)) + ), + BinOr( + LogicalNot( + BinAnd( + ValUse(3, SBoolean), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ), + EQ(ArithOp(IntConstant(1), IntConstant(0), OpCode @@ (-99.toByte)), IntConstant(1)) + ) + ) + ) + ))) + } + + property("Byte methods equivalence") { + testCases( + Seq( + (0.toByte, Success(0.toByte)), + (1.toByte, Success(1.toByte)), + (55.toByte, Success(55.toByte)), + (Byte.MaxValue, Success(Byte.MaxValue)), + (-1.toByte, Success(-1.toByte)), + (-65.toByte, Success(-65.toByte)), + (Byte.MinValue, Success(Byte.MinValue)) + ), + existingFeature( + (x: Byte) => x.toByte, "{ (x: Byte) => x.toByte }", + FuncValue(Vector((1, SByte)), ValUse(1, SByte)))) + + testCases( + Seq( + (0.toByte, Success(0.toShort)), + (1.toByte, Success(1.toShort)), + (55.toByte, Success(55.toShort)), + (Byte.MaxValue, Success(Byte.MaxValue.toShort)), + (-1.toByte, Success(-1.toShort)), + (-65.toByte, Success(-65.toShort)), + (Byte.MinValue, Success(Byte.MinValue.toShort)) + ), + existingFeature( + (x: Byte) => x.toShort, "{ (x: Byte) => x.toShort }", + FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SShort)))) + + testCases( + Seq( + (0.toByte, Success(0)), + (1.toByte, Success(1)), + (55.toByte, Success(55)), + (Byte.MaxValue, Success(Byte.MaxValue.toInt)), + (-1.toByte, Success(-1)), + (-65.toByte, Success(-65)), + (Byte.MinValue, Success(Byte.MinValue.toInt)) + ), + existingFeature( + (x: Byte) => x.toInt, "{ (x: Byte) => x.toInt }", + FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SInt)))) + + testCases( + Seq( + (0.toByte, Success(0L)), + (1.toByte, Success(1L)), + (55.toByte, Success(55L)), + (Byte.MaxValue, Success(Byte.MaxValue.toLong)), + (-1.toByte, Success(-1L)), + (-65.toByte, Success(-65L)), + (Byte.MinValue, Success(Byte.MinValue.toLong)) + ), + existingFeature( + (x: Byte) => x.toLong, "{ (x: Byte) => x.toLong }", + FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SLong)))) + + testCases( + Seq( + (0.toByte, Success(CBigInt(new BigInteger("0", 16)))), + (1.toByte, Success(CBigInt(new BigInteger("1", 16)))), + (-1.toByte, Success(CBigInt(new BigInteger("-1", 16)))), + (127.toByte, Success(CBigInt(new BigInteger("7f", 16)))), + (-128.toByte, Success(CBigInt(new BigInteger("-80", 16)))), + (90.toByte, Success(CBigInt(new BigInteger("5a", 16)))), + (-53.toByte, Success(CBigInt(new BigInteger("-35", 16)))) + ), + existingFeature( + (x: Byte) => x.toBigInt, "{ (x: Byte) => x.toBigInt }", + FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SBigInt)))) + + val n = ExactNumeric.ByteIsExactNumeric + testCases( + Seq( + ((-128.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-128.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((-128.toByte, 17.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-128.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-120.toByte, 82.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-103.toByte, 1.toByte), Success((-102.toByte, (-104.toByte, (-103.toByte, (-103.toByte, 0.toByte)))))), + ((-90.toByte, 37.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-78.toByte, -111.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-71.toByte, -44.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-53.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((-34.toByte, 8.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-24.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-1.toByte, -1.toByte), Success((-2.toByte, (0.toByte, (1.toByte, (1.toByte, 0.toByte)))))), + ((-1.toByte, 23.toByte), Success((22.toByte, (-24.toByte, (-23.toByte, (0.toByte, -1.toByte)))))), + ((0.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((0.toByte, -23.toByte), Success((-23.toByte, (23.toByte, (0.toByte, (0.toByte, 0.toByte)))))), + ((0.toByte, -1.toByte), Success((-1.toByte, (1.toByte, (0.toByte, (0.toByte, 0.toByte)))))), + ((0.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((0.toByte, 1.toByte), Success((1.toByte, (-1.toByte, (0.toByte, (0.toByte, 0.toByte)))))), + ((0.toByte, 60.toByte), Success((60.toByte, (-60.toByte, (0.toByte, (0.toByte, 0.toByte)))))), + ((0.toByte, 127.toByte), Success((127.toByte, (-127.toByte, (0.toByte, (0.toByte, 0.toByte)))))), + ((1.toByte, -1.toByte), Success((0.toByte, (2.toByte, (-1.toByte, (-1.toByte, 0.toByte)))))), + ((1.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((1.toByte, 26.toByte), Success((27.toByte, (-25.toByte, (26.toByte, (0.toByte, 1.toByte)))))), + ((7.toByte, -32.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((33.toByte, 1.toByte), Success((34.toByte, (32.toByte, (33.toByte, (33.toByte, 0.toByte)))))), + ((90.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((127.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((127.toByte, -47.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((127.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))) + ), + existingFeature( + { (x: (Byte, Byte)) => + val a = x._1; val b = x._2 + val plus = n.plus(a, b) + val minus = n.minus(a, b) + val mul = n.times(a, b) + val div = (a / b).toByteExact + val mod = (a % b).toByteExact + (plus, (minus, (mul, (div, mod)))) + }, + """{ (x: (Byte, Byte)) => + | val a = x._1; val b = x._2 + | val plus = a + b + | val minus = a - b + | val mul = a * b + | val div = a / b + | val mod = a % b + | (plus, (minus, (mul, (div, mod)))) + |}""".stripMargin, + FuncValue( + Vector((1, STuple(Vector(SByte, SByte)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[ByteValue](ValUse(1, STuple(Vector(SByte, SByte))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[ByteValue](ValUse(1, STuple(Vector(SByte, SByte))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SByte), ValUse(4, SByte), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + )) + } + + property("Byte methods equivalence (new features)") { + lazy val toBytes = newFeature((x: Byte) => x.toBytes, "{ (x: Byte) => x.toBytes }") + lazy val toBits = newFeature((x: Byte) => x.toBits, "{ (x: Byte) => x.toBits }") + lazy val toAbs = newFeature((x: Byte) => x.toAbs, "{ (x: Byte) => x.toAbs }") + lazy val compareTo = newFeature( + (x: (Byte, Byte)) => x._1.compareTo(x._2), + "{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }") + + lazy val bitOr = newFeature( + { (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }, + "{ (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }") + + lazy val bitAnd = newFeature( + { (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }, + "{ (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }") + + forAll { x: Byte => + Seq(toBytes, toBits, toAbs).foreach(f => f.checkEquality(x)) + } + + forAll { x: (Byte, Byte) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + property("Short methods equivalence") { + testCases( + Seq( + (Short.MinValue, Failure(new ArithmeticException("Byte overflow"))), + (-21626.toShort, Failure(new ArithmeticException("Byte overflow"))), + (Byte.MinValue.toShort, Success(Byte.MinValue)), + (-1.toShort, Success(-1.toByte)), + (0.toShort, Success(0.toByte)), + (1.toShort, Success(1.toByte)), + (Byte.MaxValue.toShort, Success(Byte.MaxValue)), + (11768.toShort, Failure(new ArithmeticException("Byte overflow"))), + (Short.MaxValue, Failure(new ArithmeticException("Byte overflow"))) + ), + existingFeature((x: Short) => x.toByteExact, + "{ (x: Short) => x.toByte }", + FuncValue(Vector((1, SShort)), Downcast(ValUse(1, SShort), SByte)))) + + testCases( + Seq( + (-32768.toShort, Success(-32768.toShort)), + (-27798.toShort, Success(-27798.toShort)), + (-1.toShort, Success(-1.toShort)), + (0.toShort, Success(0.toShort)), + (1.toShort, Success(1.toShort)), + (27929.toShort, Success(27929.toShort)), + (32767.toShort, Success(32767.toShort)) + ), + existingFeature((x: Short) => x.toShort, + "{ (x: Short) => x.toShort }", + FuncValue(Vector((1, SShort)), ValUse(1, SShort)))) + + testCases( + Seq( + (-32768.toShort, Success(-32768)), + (-21064.toShort, Success(-21064)), + (-1.toShort, Success(-1)), + (0.toShort, Success(0)), + (1.toShort, Success(1)), + (18388.toShort, Success(18388)), + (32767.toShort, Success(32767)) + ), + existingFeature((x: Short) => x.toInt, + "{ (x: Short) => x.toInt }", + FuncValue(Vector((1, SShort)), Upcast(ValUse(1, SShort), SInt)))) + + testCases( + Seq( + (-32768.toShort, Success(-32768L)), + (-23408.toShort, Success(-23408L)), + (-1.toShort, Success(-1L)), + (0.toShort, Success(0L)), + (1.toShort, Success(1L)), + (23318.toShort, Success(23318L)), + (32767.toShort, Success(32767L)) + ), + existingFeature((x: Short) => x.toLong, + "{ (x: Short) => x.toLong }", + FuncValue(Vector((1, SShort)), Upcast(ValUse(1, SShort), SLong)))) + + testCases( + Seq( + (-32768.toShort, Success(CBigInt(new BigInteger("-8000", 16)))), + (-26248.toShort, Success(CBigInt(new BigInteger("-6688", 16)))), + (-1.toShort, Success(CBigInt(new BigInteger("-1", 16)))), + (0.toShort, Success(CBigInt(new BigInteger("0", 16)))), + (1.toShort, Success(CBigInt(new BigInteger("1", 16)))), + (22845.toShort, Success(CBigInt(new BigInteger("593d", 16)))), + (32767.toShort, Success(CBigInt(new BigInteger("7fff", 16)))) + ), + existingFeature((x: Short) => x.toBigInt, + "{ (x: Short) => x.toBigInt }", + FuncValue(Vector((1, SShort)), Upcast(ValUse(1, SShort), SBigInt)))) + + val n = ExactNumeric.ShortIsExactNumeric + testCases( + Seq( + ((-32768.toShort, 1.toShort), Failure(new ArithmeticException("Short overflow"))), + ((-32768.toShort, 4006.toShort), Failure(new ArithmeticException("Short overflow"))), + ((-21384.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((-19027.toShort, 6073.toShort), Failure(new ArithmeticException("Short overflow"))), + ((-16800.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))), + ((-1.toShort, -30005.toShort), Success((-30006.toShort, (30004.toShort, (30005.toShort, (0.toShort, -1.toShort)))))), + ((-1.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((0.toShort, -1.toShort), Success((-1.toShort, (1.toShort, (0.toShort, (0.toShort, 0.toShort)))))), + ((0.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((0.toShort, 1.toShort), Success((1.toShort, (-1.toShort, (0.toShort, (0.toShort, 0.toShort)))))), + ((0.toShort, 25105.toShort), Success((25105.toShort, (-25105.toShort, (0.toShort, (0.toShort, 0.toShort)))))), + ((1.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((1.toShort, -1.toShort), Success((0.toShort, (2.toShort, (-1.toShort, (-1.toShort, 0.toShort)))))), + ((1.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((605.toShort, 7698.toShort), Failure(new ArithmeticException("Short overflow"))), + ((5094.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((5350.toShort, -1.toShort), Success((5349.toShort, (5351.toShort, (-5350.toShort, (-5350.toShort, 0.toShort)))))), + ((8115.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((14217.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))), + ((16223.toShort, -11686.toShort), Failure(new ArithmeticException("Short overflow"))), + ((16989.toShort, 1.toShort), Success((16990.toShort, (16988.toShort, (16989.toShort, (16989.toShort, 0.toShort)))))), + ((20397.toShort, -4450.toShort), Failure(new ArithmeticException("Short overflow"))), + ((20488.toShort, 1.toShort), Success((20489.toShort, (20487.toShort, (20488.toShort, (20488.toShort, 0.toShort)))))), + ((32767.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((32767.toShort, -13423.toShort), Failure(new ArithmeticException("Short overflow"))), + ((32767.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))) + ), + existingFeature( + { (x: (Short, Short)) => + val a = x._1; val b = x._2 + val plus = n.plus(a, b) + val minus = n.minus(a, b) + val mul = n.times(a, b) + val div = (a / b).toShortExact + val mod = (a % b).toShortExact + (plus, (minus, (mul, (div, mod)))) + }, + """{ (x: (Short, Short)) => + | val a = x._1; val b = x._2 + | val plus = a + b + | val minus = a - b + | val mul = a * b + | val div = a / b + | val mod = a % b + | (plus, (minus, (mul, (div, mod)))) + |}""".stripMargin, + FuncValue( + Vector((1, STuple(Vector(SShort, SShort)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[ShortValue](ValUse(1, STuple(Vector(SShort, SShort))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[ShortValue](ValUse(1, STuple(Vector(SShort, SShort))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SShort), ValUse(4, SShort), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SShort), ValUse(4, SShort), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SShort), ValUse(4, SShort), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SShort), ValUse(4, SShort), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SShort), ValUse(4, SShort), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + )) + } + + property("Short methods equivalence (new features)") { + lazy val toBytes = newFeature((x: Short) => x.toBytes, "{ (x: Short) => x.toBytes }") + lazy val toBits = newFeature((x: Short) => x.toBits, "{ (x: Short) => x.toBits }") + lazy val toAbs = newFeature((x: Short) => x.toAbs, "{ (x: Short) => x.toAbs }") + + lazy val compareTo = newFeature((x: (Short, Short)) => x._1.compareTo(x._2), + "{ (x: (Short, Short)) => x._1.compareTo(x._2) }") + + lazy val bitOr = newFeature( + { (x: (Short, Short)) => (x._1 | x._2).toShortExact }, + "{ (x: (Short, Short)) => x._1 | x._2 }") + + lazy val bitAnd = newFeature( + { (x: (Short, Short)) => (x._1 & x._2).toShortExact }, + "{ (x: (Short, Short)) => x._1 & x._2 }") + + forAll { x: Short => + Seq(toBytes, toBits, toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Short, Short) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + property("Int methods equivalence") { + testCases( + Seq( + (Int.MinValue, Failure(new ArithmeticException("Byte overflow"))), + (-2014394379, Failure(new ArithmeticException("Byte overflow"))), + (Byte.MinValue.toInt, Success(Byte.MinValue)), + (-1, Success(-1.toByte)), + (0, Success(0.toByte)), + (1, Success(1.toByte)), + (Byte.MaxValue.toInt, Success(Byte.MaxValue)), + (181686429, Failure(new ArithmeticException("Byte overflow"))), + (Int.MaxValue, Failure(new ArithmeticException("Byte overflow"))) + ), + existingFeature((x: Int) => x.toByteExact, + "{ (x: Int) => x.toByte }", + FuncValue(Vector((1, SInt)), Downcast(ValUse(1, SInt), SByte)))) + + testCases( + Seq( + (Int.MinValue, Failure(new ArithmeticException("Short overflow"))), + (Short.MinValue - 1, Failure(new ArithmeticException("Short overflow"))), + (Short.MinValue.toInt, Success(Short.MinValue)), + (-1, Success(-1.toShort)), + (0, Success(0.toShort)), + (1, Success(1.toShort)), + (Short.MaxValue.toInt, Success(Short.MaxValue)), + (Short.MaxValue + 1, Failure(new ArithmeticException("Short overflow"))), + (Int.MaxValue, Failure(new ArithmeticException("Short overflow"))) + ), + existingFeature((x: Int) => x.toShortExact, + "{ (x: Int) => x.toShort }", + FuncValue(Vector((1, SInt)), Downcast(ValUse(1, SInt), SShort)))) + + testCases( + Seq( + (Int.MinValue, Success(Int.MinValue)), + (-1, Success(-1)), + (0, Success(0)), + (1, Success(1)), + (Int.MaxValue, Success(Int.MaxValue)) + ), + existingFeature((x: Int) => x.toInt, + "{ (x: Int) => x.toInt }", + FuncValue(Vector((1, SInt)), ValUse(1, SInt)))) + + testCases( + Seq( + (Int.MinValue, Success(Int.MinValue.toLong)), + (-1, Success(-1L)), + (0, Success(0L)), + (1, Success(1L)), + (Int.MaxValue, Success(Int.MaxValue.toLong)) + ), + existingFeature((x: Int) => x.toLong, + "{ (x: Int) => x.toLong }", + FuncValue(Vector((1, SInt)), Upcast(ValUse(1, SInt), SLong)))) + + testCases( + Seq( + (Int.MinValue, Success(CBigInt(new BigInteger("-80000000", 16)))), + (-1937187314, Success(CBigInt(new BigInteger("-737721f2", 16)))), + (-1, Success(CBigInt(new BigInteger("-1", 16)))), + (0, Success(CBigInt(new BigInteger("0", 16)))), + (1, Success(CBigInt(new BigInteger("1", 16)))), + (1542171288, Success(CBigInt(new BigInteger("5bebaa98", 16)))), + (Int.MaxValue, Success(CBigInt(new BigInteger("7fffffff", 16)))) + ), + existingFeature((x: Int) => x.toBigInt, + "{ (x: Int) => x.toBigInt }", + FuncValue(Vector((1, SInt)), Upcast(ValUse(1, SInt), SBigInt)))) + + val n = ExactNumeric.IntIsExactNumeric + testCases( + Seq( + ((Int.MinValue, 449583993), Failure(new ArithmeticException("integer overflow"))), + ((-1589633733, 2147483647), Failure(new ArithmeticException("integer overflow"))), + ((-1585471506, -1), Success((-1585471507, (-1585471505, (1585471506, (1585471506, 0)))))), + ((-1569005179, 1230236634), Failure(new ArithmeticException("integer overflow"))), + ((-1493733356, -1319619597), Failure(new ArithmeticException("integer overflow"))), + ((-1100263120, -880052091), Failure(new ArithmeticException("integer overflow"))), + ((-1055955857, 309147303), Failure(new ArithmeticException("integer overflow"))), + ((-569807371, 0), Failure(new ArithmeticException("/ by zero"))), + ((-522264843, 2147483647), Failure(new ArithmeticException("integer overflow"))), + ((-109552389, 0), Failure(new ArithmeticException("/ by zero"))), + ((-1, -2147483648), Failure(new ArithmeticException("integer overflow"))), + ((-1, -1), Success((-2, (0, (1, (1, 0)))))), + ((-1, 0), Failure(new ArithmeticException("/ by zero"))), + ((0, -2147483648), Failure(new ArithmeticException("integer overflow"))), + ((1, -1525049432), Success((-1525049431, (1525049433, (-1525049432, (0, 1)))))), + ((1, 0), Failure(new ArithmeticException("/ by zero"))), + ((1, 805353746), Success((805353747, (-805353745, (805353746, (0, 1)))))), + ((1, 2147483647), Failure(new ArithmeticException("integer overflow"))), + ((475797978, 0), Failure(new ArithmeticException("/ by zero"))), + ((782343922, -1448560539), Failure(new ArithmeticException("integer overflow"))), + ((928769361, 542647292), Failure(new ArithmeticException("integer overflow"))), + ((1568062151, 0), Failure(new ArithmeticException("/ by zero"))), + ((1698252401, -1), Success((1698252400, (1698252402, (-1698252401, (-1698252401, 0)))))), + ((1949795740, -1575667037), Failure(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, -1), Failure(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, 1), Failure(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, 1738276576), Failure(new ArithmeticException("integer overflow"))) + ), + existingFeature( + { (x: (Int, Int)) => + val a = x._1; val b = x._2 + val plus = n.plus(a, b) + val minus = n.minus(a, b) + val mul = n.times(a, b) + val div = a / b + val mod = a % b + (plus, (minus, (mul, (div, mod)))) + }, + """{ (x: (Int, Int)) => + | val a = x._1; val b = x._2 + | val plus = a + b + | val minus = a - b + | val mul = a * b + | val div = a / b + | val mod = a % b + | (plus, (minus, (mul, (div, mod)))) + |}""".stripMargin, + FuncValue( + Vector((1, STuple(Vector(SInt, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[IntValue](ValUse(1, STuple(Vector(SInt, SInt))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[IntValue](ValUse(1, STuple(Vector(SInt, SInt))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SInt), ValUse(4, SInt), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SInt), ValUse(4, SInt), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SInt), ValUse(4, SInt), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SInt), ValUse(4, SInt), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SInt), ValUse(4, SInt), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ))) + } + + property("Int methods equivalence (new features)") { + lazy val toBytes = newFeature((x: Int) => x.toBytes, "{ (x: Int) => x.toBytes }") + lazy val toBits = newFeature((x: Int) => x.toBits, "{ (x: Int) => x.toBits }") + lazy val toAbs = newFeature((x: Int) => x.toAbs, "{ (x: Int) => x.toAbs }") + lazy val compareTo = newFeature((x: (Int, Int)) => x._1.compareTo(x._2), + "{ (x: (Int, Int)) => x._1.compareTo(x._2) }") + + lazy val bitOr = newFeature( + { (x: (Int, Int)) => x._1 | x._2 }, + "{ (x: (Int, Int)) => x._1 | x._2 }") + + lazy val bitAnd = newFeature( + { (x: (Int, Int)) => x._1 & x._2 }, + "{ (x: (Int, Int)) => x._1 & x._2 }") + + forAll { x: Int => + Seq(toBytes, toBits, toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Int, Int) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + property("Long methods equivalence") { + testCases( + Seq( + (Long.MinValue, Failure(new ArithmeticException("Byte overflow"))), + (Byte.MinValue.toLong - 1, Failure(new ArithmeticException("Byte overflow"))), + (Byte.MinValue.toLong, Success(Byte.MinValue)), + (-1L, Success(-1.toByte)), + (0L, Success(0.toByte)), + (1L, Success(1.toByte)), + (Byte.MaxValue.toLong, Success(Byte.MaxValue)), + (Byte.MaxValue.toLong + 1, Failure(new ArithmeticException("Byte overflow"))), + (Long.MinValue, Failure(new ArithmeticException("Byte overflow"))) + ), + existingFeature((x: Long) => x.toByteExact, + "{ (x: Long) => x.toByte }", + FuncValue(Vector((1, SLong)), Downcast(ValUse(1, SLong), SByte)))) + + testCases( + Seq( + (Long.MinValue, Failure(new ArithmeticException("Short overflow"))), + (Short.MinValue.toLong - 1, Failure(new ArithmeticException("Short overflow"))), + (Short.MinValue.toLong, Success(Short.MinValue)), + (-1L, Success(-1.toShort)), + (0L, Success(0.toShort)), + (1L, Success(1.toShort)), + (Short.MaxValue.toLong, Success(Short.MaxValue)), + (Short.MaxValue.toLong + 1, Failure(new ArithmeticException("Short overflow"))), + (Long.MinValue, Failure(new ArithmeticException("Short overflow"))) + ), + existingFeature((x: Long) => x.toShortExact, + "{ (x: Long) => x.toShort }", + FuncValue(Vector((1, SLong)), Downcast(ValUse(1, SLong), SShort)))) + + testCases( + Seq( + (Long.MinValue, Failure(new ArithmeticException("Int overflow"))), + (Int.MinValue.toLong - 1, Failure(new ArithmeticException("Int overflow"))), + (Int.MinValue.toLong, Success(Int.MinValue)), + (-1L, Success(-1.toInt)), + (0L, Success(0.toInt)), + (1L, Success(1.toInt)), + (Int.MaxValue.toLong, Success(Int.MaxValue)), + (Int.MaxValue.toLong + 1, Failure(new ArithmeticException("Int overflow"))), + (Long.MinValue, Failure(new ArithmeticException("Int overflow"))) + ), + existingFeature((x: Long) => x.toIntExact, + "{ (x: Long) => x.toInt }", + FuncValue(Vector((1, SLong)), Downcast(ValUse(1, SLong), SInt)))) + + testCases( + Seq( + (Long.MinValue, Success(Long.MinValue)), + (-1L, Success(-1L)), + (0L, Success(0L)), + (1L, Success(1L)), + (Long.MaxValue, Success(Long.MaxValue)) + ), + existingFeature((x: Long) => x.toLong, + "{ (x: Long) => x.toLong }", + FuncValue(Vector((1, SLong)), ValUse(1, SLong)))) + + testCases( + Seq( + (Long.MinValue, Success(CBigInt(new BigInteger("-8000000000000000", 16)))), + (-1074651039980347209L, Success(CBigInt(new BigInteger("-ee9ed6d57885f49", 16)))), + (-1L, Success(CBigInt(new BigInteger("-1", 16)))), + (0L, Success(CBigInt(new BigInteger("0", 16)))), + (1L, Success(CBigInt(new BigInteger("1", 16)))), + (1542942726564696512L, Success(CBigInt(new BigInteger("1569a23c25a951c0", 16)))), + (Long.MaxValue, Success(CBigInt(new BigInteger("7fffffffffffffff", 16)))) + ), + existingFeature((x: Long) => x.toBigInt, + "{ (x: Long) => x.toBigInt }", + FuncValue(Vector((1, SLong)), Upcast(ValUse(1, SLong), SBigInt)))) + + val n = ExactNumeric.LongIsExactNumeric + testCases( + Seq( + ((Long.MinValue, -4677100190307931395L), Failure(new ArithmeticException("long overflow"))), + ((Long.MinValue, -1L), Failure(new ArithmeticException("long overflow"))), + ((Long.MinValue, 1L), Failure(new ArithmeticException("long overflow"))), + ((-9223372036854775808L, 0L), Failure(new ArithmeticException("/ by zero"))), + ((-5828066432064138816L, 9105034716270510411L), Failure(new ArithmeticException("long overflow"))), + ((-4564956247298949325L, -1L), Success( + (-4564956247298949326L, (-4564956247298949324L, (4564956247298949325L, (4564956247298949325L, 0L)))) + )), + ((-1499553565058783253L, -3237683216870282569L), Failure(new ArithmeticException("long overflow"))), + ((-1368457031689886112L, 9223372036854775807L), Failure(new ArithmeticException("long overflow"))), + ((-1L, -4354407074688367443L), Success((-4354407074688367444L, (4354407074688367442L, (4354407074688367443L, (0L, -1L)))))), + ((-1L, -1L), Success((-2L, (0L, (1L, (1L, 0L)))))), + ((-1L, 5665019549505434695L), Success((5665019549505434694L, (-5665019549505434696L, (-5665019549505434695L, (0L, -1L)))))), + ((0L, -1L), Success((-1L, (1L, (0L, (0L, 0L)))))), + ((0L, 0L), Failure(new ArithmeticException("/ by zero"))), + ((0L, 2112386634269044172L), Success((2112386634269044172L, (-2112386634269044172L, (0L, (0L, 0L)))))), + ((2254604056782701370L, -5878231674026236574L), Failure(new ArithmeticException("long overflow"))), + ((2903872550238813643L, 1L), Success( + (2903872550238813644L, (2903872550238813642L, (2903872550238813643L, (2903872550238813643L, 0L)))) + )), + ((5091129735284641762L, -427673944382373638L), Failure(new ArithmeticException("long overflow"))), + ((6029085020194630780L, 2261786144956037939L), Failure(new ArithmeticException("long overflow"))), + ((8126382074515995418L, -4746652047588907829L), Failure(new ArithmeticException("long overflow"))), + ((Long.MaxValue, 1L), Failure(new ArithmeticException("long overflow"))), + ((Long.MaxValue, -1L), Failure(new ArithmeticException("long overflow"))) + ), + existingFeature( + { (x: (Long, Long)) => + val a = x._1; val b = x._2 + val plus = n.plus(a, b) + val minus = n.minus(a, b) + val mul = n.times(a, b) + val div = a / b + val mod = a % b + (plus, (minus, (mul, (div, mod)))) + }, + """{ (x: (Long, Long)) => + | val a = x._1; val b = x._2 + | val plus = a + b + | val minus = a - b + | val mul = a * b + | val div = a / b + | val mod = a % b + | (plus, (minus, (mul, (div, mod)))) + |}""".stripMargin, + FuncValue( + Vector((1, STuple(Vector(SLong, SLong)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[LongValue](ValUse(1, STuple(Vector(SLong, SLong))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[LongValue](ValUse(1, STuple(Vector(SLong, SLong))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SLong), ValUse(4, SLong), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SLong), ValUse(4, SLong), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SLong), ValUse(4, SLong), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SLong), ValUse(4, SLong), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SLong), ValUse(4, SLong), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ))) + } + + property("Long methods equivalence (new features)") { + lazy val toBytes = newFeature((x: Long) => x.toBytes, "{ (x: Long) => x.toBytes }") + lazy val toBits = newFeature((x: Long) => x.toBits, "{ (x: Long) => x.toBits }") + lazy val toAbs = newFeature((x: Long) => x.toAbs, "{ (x: Long) => x.toAbs }") + + lazy val compareTo = newFeature((x: (Long, Long)) => x._1.compareTo(x._2), + "{ (x: (Long, Long)) => x._1.compareTo(x._2) }") + + lazy val bitOr = newFeature( + { (x: (Long, Long)) => x._1 | x._2 }, + "{ (x: (Long, Long)) => x._1 | x._2 }") + + lazy val bitAnd = newFeature( + { (x: (Long, Long)) => x._1 & x._2 }, + "{ (x: (Long, Long)) => x._1 & x._2 }") + + forAll { x: Long => + Seq(toBytes, toBits, toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (Long, Long) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + property("BigInt methods equivalence") { + testCases( + Seq( + (CBigInt(new BigInteger("-85102d7f884ca0e8f56193b46133acaf7e4681e1757d03f191ae4f445c8e0", 16)), Success( + CBigInt(new BigInteger("-85102d7f884ca0e8f56193b46133acaf7e4681e1757d03f191ae4f445c8e0", 16)) + )), + (CBigInt(new BigInteger("-8000000000000000", 16)), Success(CBigInt(new BigInteger("-8000000000000000", 16)))), + (CBigInt(new BigInteger("-1", 16)), Success(CBigInt(new BigInteger("-1", 16)))), + (CBigInt(new BigInteger("0", 16)), Success(CBigInt(new BigInteger("0", 16)))), + (CBigInt(new BigInteger("1", 16)), Success(CBigInt(new BigInteger("1", 16)))), + (CBigInt(new BigInteger("7fffffffffffffff", 16)), Success(CBigInt(new BigInteger("7fffffffffffffff", 16)))), + (CBigInt(new BigInteger("bdd56c22eb3eace8bc4e1c38c65dfdb2e4ffdcf421ae78c36b93b9ff37dc0", 16)), Success( + CBigInt(new BigInteger("bdd56c22eb3eace8bc4e1c38c65dfdb2e4ffdcf421ae78c36b93b9ff37dc0", 16)) + )) + ), + existingFeature((x: BigInt) => x, + "{ (x: BigInt) => x.toBigInt }", + FuncValue(Vector((1, SBigInt)), ValUse(1, SBigInt)))) + + val n = NumericOps.BigIntIsExactNumeric + testCases( + Seq( + ((CBigInt(new BigInteger("-8683d1cd99d5fcf0e6eff6295c285c36526190e13dbde008c49e5ae6fddc1c", 16)), + CBigInt(new BigInteger("-2ef55db3f245feddacf0182e299dd", 16))), + Failure(new ArithmeticException("BigInteger out of 256 bit range"))), + + ((CBigInt(new BigInteger("-68e1136872f98fb0245ec5aa4bef46e16273e860746c892", 16)), + CBigInt(new BigInteger("-352aaa769b41a327", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("-39fc00ebf09080cbd8408dd38c4b7490bea533447047140", 16)), + CBigInt(new BigInteger("31de9e96177dbd39", 16))), + Success(( + CBigInt(new BigInteger("-39fc00ebf09080cbd8408dd38c4b748da0bb49e2f86b407", 16)), + (CBigInt(new BigInteger("-39fc00ebf09080cbd8408dd38c4b7493dc8f1ca5e822e79", 16)), + (CBigInt(new BigInteger("-b4ba8a17d328dac74ef014d7be35597a1259f8b16f0ff1c9820dea23d97740", 16)), + (CBigInt(new BigInteger("-129a8045376e104f0d3771b6c2c128fc", 16)), + CBigInt(new BigInteger("12fe89836fc97815", 16)))))) )), + + ((CBigInt(new BigInteger("-8000000000000000", 16)), CBigInt(new BigInteger("8000000000000000", 16))), + Success(( + CBigInt(new BigInteger("0", 16)), + (CBigInt(new BigInteger("-10000000000000000", 16)), + (CBigInt(new BigInteger("-40000000000000000000000000000000", 16)), + (CBigInt(new BigInteger("-1", 16)), CBigInt(new BigInteger("0", 16)))))) )), + + ((CBigInt(new BigInteger("-47dede8d3e4804bb", 16)), CBigInt(new BigInteger("-388828eb6dfce683", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("-4fde491150ea00d", 16)), CBigInt(new BigInteger("-80000001", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("-80000001", 16)), CBigInt(new BigInteger("-80000001", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("0", 16)), CBigInt(new BigInteger("-8000000000000000", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("0", 16)), CBigInt(new BigInteger("0", 16))), + Failure(new ArithmeticException("BigInteger divide by zero"))), + + ((CBigInt(new BigInteger("1", 16)), + CBigInt(new BigInteger("-86063f66e06d6d535c95862cd506309a95d10102422fee", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("80000000", 16)), CBigInt(new BigInteger("4e592ce5b544b8f7a91f97ec9ea2f2c3660111360297a4", 16))), + Success(( + CBigInt(new BigInteger("4e592ce5b544b8f7a91f97ec9ea2f2c3660111b60297a4", 16)), + (CBigInt(new BigInteger("-4e592ce5b544b8f7a91f97ec9ea2f2c3660110b60297a4", 16)), + (CBigInt(new BigInteger("272c9672daa25c7bd48fcbf64f517961b300889b014bd200000000", 16)), + (CBigInt(new BigInteger("0", 16)), CBigInt(new BigInteger("80000000", 16)))))) )), + + ((CBigInt(new BigInteger("3d31398dc4783303", 16)), + CBigInt(new BigInteger("-37b381db4e6e927e202a2a421d5a09ca", 16))), + Failure(new ArithmeticException("BigInteger: modulus not positive"))), + + ((CBigInt(new BigInteger("5524814a26357cb71488b6fb26af2d3", 16)), + CBigInt(new BigInteger("c413b7d975a9972427f46996299fe57cfe79479ac954a7", 16))), + Failure(new ArithmeticException("BigInteger out of 256 bit range"))) + ), + existingFeature( + { (x: (BigInt, BigInt)) => + val a = x._1; val b = x._2 + val plus = n.plus(a, b) + val minus = n.minus(a, b) + val mul = n.times(a, b) + val div = a / b + val mod = a % b + (plus, (minus, (mul, (div, mod)))) + }, + """{ (x: (BigInt, BigInt)) => + | val a = x._1; val b = x._2 + | val plus = a + b + | val minus = a - b + | val mul = a * b + | val div = a / b + | val mod = a % b + | (plus, (minus, (mul, (div, mod)))) + |}""".stripMargin, + FuncValue( + Vector((1, STuple(Vector(SBigInt, SBigInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[BigIntValue](ValUse(1, STuple(Vector(SBigInt, SBigInt))), 1.toByte) + ), + ValDef( + 4, + List(), + SelectField.typed[BigIntValue](ValUse(1, STuple(Vector(SBigInt, SBigInt))), 2.toByte) + ) + ), + Tuple( + Vector( + ArithOp(ValUse(3, SBigInt), ValUse(4, SBigInt), OpCode @@ (-102.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SBigInt), ValUse(4, SBigInt), OpCode @@ (-103.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SBigInt), ValUse(4, SBigInt), OpCode @@ (-100.toByte)), + Tuple( + Vector( + ArithOp(ValUse(3, SBigInt), ValUse(4, SBigInt), OpCode @@ (-99.toByte)), + ArithOp(ValUse(3, SBigInt), ValUse(4, SBigInt), OpCode @@ (-98.toByte)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ), true) + } + + property("BigInt methods equivalence (new features)") { + val toByte = newFeature((x: BigInt) => x.toByte, + "{ (x: BigInt) => x.toByte }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SByte))) + + val toShort = newFeature((x: BigInt) => x.toShort, + "{ (x: BigInt) => x.toShort }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SShort))) + + val toInt = newFeature((x: BigInt) => x.toInt, + "{ (x: BigInt) => x.toInt }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SInt))) + + val toLong = newFeature((x: BigInt) => x.toLong, + "{ (x: BigInt) => x.toLong }", + FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SLong))) + + lazy val toBytes = newFeature((x: BigInt) => x.toBytes, "{ (x: BigInt) => x.toBytes }") + lazy val toBits = newFeature((x: BigInt) => x.toBits, "{ (x: BigInt) => x.toBits }") + lazy val toAbs = newFeature((x: BigInt) => x.toAbs, "{ (x: BigInt) => x.toAbs }") + + lazy val compareTo = newFeature((x: (BigInt, BigInt)) => x._1.compareTo(x._2), + "{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }") + + lazy val bitOr = newFeature({ (x: (BigInt, BigInt)) => x._1 | x._2 }, + "{ (x: (BigInt, BigInt)) => x._1 | x._2 }") + + lazy val bitAnd = newFeature({ (x: (BigInt, BigInt)) => x._1 & x._2 }, + "{ (x: (BigInt, BigInt)) => x._1 & x._2 }") + + forAll { x: BigInt => + Seq(toByte, toShort, toInt, toLong, toBytes, toBits, toAbs).foreach(_.checkEquality(x)) + } + forAll { x: (BigInt, BigInt) => + Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) + } + } + + property("GroupElement methods equivalence") { + val ge1 = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" + val ge2 = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" + val ge3 = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" + + testCases( + Seq( + (Helpers.decodeGroupElement(ge1), Success(Helpers.decodeBytes(ge1))), + (Helpers.decodeGroupElement(ge2), Success(Helpers.decodeBytes(ge2))), + (Helpers.decodeGroupElement(ge3), Success(Helpers.decodeBytes(ge3))), + (SigmaDsl.groupGenerator, + Success(Helpers.decodeBytes("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (SigmaDsl.groupIdentity, + Success(Helpers.decodeBytes("000000000000000000000000000000000000000000000000000000000000000000"))) + ), + existingFeature((x: GroupElement) => x.getEncoded, + "{ (x: GroupElement) => x.getEncoded }", + FuncValue( + Vector((1, SGroupElement)), + MethodCall(ValUse(1, SGroupElement), SGroupElement.getMethodByName("getEncoded"), Vector(), Map()) + ))) + + testCases( + Seq( + (Helpers.decodeGroupElement(ge1), Success(true)), + (Helpers.decodeGroupElement(ge2), Success(true)), + (Helpers.decodeGroupElement(ge3), Success(true)), + (SigmaDsl.groupGenerator, Success(true)), + (SigmaDsl.groupIdentity, Success(true)) + ), + existingFeature({ (x: GroupElement) => decodePoint(x.getEncoded) == x }, + "{ (x: GroupElement) => decodePoint(x.getEncoded) == x }", + FuncValue( + Vector((1, SGroupElement)), + EQ( + DecodePoint( + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SGroupElement), + SGroupElement.getMethodByName("getEncoded"), + Vector(), + Map() + ) + ), + ValUse(1, SGroupElement) + ) + ))) + + testCases( + Seq( + (Helpers.decodeGroupElement(ge1), Success(Helpers.decodeGroupElement("02358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056"))), + (Helpers.decodeGroupElement(ge2), Success(Helpers.decodeGroupElement("03dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575"))), + (Helpers.decodeGroupElement(ge3), Success(Helpers.decodeGroupElement("0390449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d"))), + (SigmaDsl.groupGenerator, Success(Helpers.decodeGroupElement("0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (SigmaDsl.groupIdentity, Success(Helpers.decodeGroupElement("000000000000000000000000000000000000000000000000000000000000000000"))) + ), + existingFeature({ (x: GroupElement) => x.negate }, + "{ (x: GroupElement) => x.negate }", + FuncValue( + Vector((1, SGroupElement)), + MethodCall(ValUse(1, SGroupElement), SGroupElement.getMethodByName("negate"), Vector(), Map()) + ))) + + //TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // val isIdentity = existingFeature({ (x: GroupElement) => x.isIdentity }, + // "{ (x: GroupElement) => x.isIdentity }") + + testCases( + Seq( + ((Helpers.decodeGroupElement(ge1), CBigInt(new BigInteger("-25c80b560dd7844e2efd10f80f7ee57d", 16))), + Success(Helpers.decodeGroupElement("023a850181b7b73f92a5bbfa0bfc78f5bbb6ff00645ddde501037017e1a2251e2e"))), + ((Helpers.decodeGroupElement(ge2), CBigInt(new BigInteger("2488741265082fb02b09f992be3dd8d60d2bbe80d9e2630", 16))), + Success(Helpers.decodeGroupElement("032045b928fb7774a4cd9ef5fa8209f4e493cd4cc5bd536b52746a53871bf73431"))), + ((Helpers.decodeGroupElement(ge3), CBigInt(new BigInteger("-33e8fbdb13d2982e92583445e1fdcb5901a178a7aa1e100", 16))), + Success(Helpers.decodeGroupElement("036128efaf14d8ac2812a662f6494dc617b87986a3dc6b4a59440048a7ac7d2729"))), + ((Helpers.decodeGroupElement(ge3), CBigInt(new BigInteger("1", 16))), + Success(Helpers.decodeGroupElement(ge3))) + ), + existingFeature({ (x: (GroupElement, BigInt)) => x._1.exp(x._2) }, + "{ (x: (GroupElement, BigInt)) => x._1.exp(x._2) }", + FuncValue( + Vector((1, STuple(Vector(SGroupElement, SBigInt)))), + Exponentiate( + SelectField.typed[Value[SGroupElement.type]]( + ValUse(1, STuple(Vector(SGroupElement, SBigInt))), + 1.toByte + ), + SelectField.typed[Value[SBigInt.type]]( + ValUse(1, STuple(Vector(SGroupElement, SBigInt))), + 2.toByte + ) + ) + ))) + + testCases( + Seq( + ((Helpers.decodeGroupElement(ge1), Helpers.decodeGroupElement("03e132ca090614bd6c9f811e91f6daae61f16968a1e6c694ed65aacd1b1092320e")), + Success(Helpers.decodeGroupElement("02bc48937b4a66f249a32dfb4d2efd0743dc88d46d770b8c5d39fd03325ba211df"))), + ((Helpers.decodeGroupElement(ge2), Helpers.decodeGroupElement("03e132ca090614bd6c9f811e91f6daae61f16968a1e6c694ed65aacd1b1092320e")), + Success(Helpers.decodeGroupElement("0359c3bb2ac4ea4dbd7b1e09d7b11198141a3263834fb84a88039629ec1e9311d1"))), + ((Helpers.decodeGroupElement(ge3), Helpers.decodeGroupElement("03e132ca090614bd6c9f811e91f6daae61f16968a1e6c694ed65aacd1b1092320e")), + Success(Helpers.decodeGroupElement("02eca42e28548d3fb9fa77cdd0c983066c3ad141ebb086b5044ce46b9ba9b5a714"))), + ((Helpers.decodeGroupElement(ge3), SigmaDsl.groupIdentity), + Success(Helpers.decodeGroupElement(ge3))) + ), + existingFeature({ (x: (GroupElement, GroupElement)) => x._1.multiply(x._2) }, + "{ (x: (GroupElement, GroupElement)) => x._1.multiply(x._2) }", + FuncValue( + Vector((1, STuple(Vector(SGroupElement, SGroupElement)))), + MultiplyGroup( + SelectField.typed[Value[SGroupElement.type]]( + ValUse(1, STuple(Vector(SGroupElement, SGroupElement))), + 1.toByte + ), + SelectField.typed[Value[SGroupElement.type]]( + ValUse(1, STuple(Vector(SGroupElement, SGroupElement))), + 2.toByte + ) + ) + ))) + } + + property("AvlTree properties equivalence") { + def expectedExprFor(propName: String) = { + FuncValue( + Vector((1, SAvlTree)), + MethodCall( + ValUse(1, SAvlTree), + SAvlTree.getMethodByName(propName), + Vector(), + Map() + ) + ) + } + val t1 = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"), + AvlTreeFlags(false, true, true), + 1, + Some(1) + ) + ) + val t2 = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), + AvlTreeFlags(false, false, false), + 32, + Some(64) + ) + ) + val t3 = CAvlTree( + AvlTreeData( + ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), + AvlTreeFlags(true, false, false), + 128, + None + ) + ) + + testCases( + Seq( + (t1, Success(Helpers.decodeBytes("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"))), + (t2, Success(Helpers.decodeBytes("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"))), + (t3, Success(Helpers.decodeBytes("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"))) + ), + existingFeature((t: AvlTree) => t.digest, + "{ (t: AvlTree) => t.digest }", + expectedExprFor("digest"))) + + testCases( + Seq( + (t1, Success(6.toByte)), + (t2, Success(0.toByte)), + (t3, Success(1.toByte)) + ), + existingFeature((t: AvlTree) => t.enabledOperations, + "{ (t: AvlTree) => t.enabledOperations }", + expectedExprFor("enabledOperations"))) + + testCases( + Seq( + (t1, Success(1)), + (t2, Success(32)), + (t3, Success(128)) + ), + existingFeature((t: AvlTree) => t.keyLength, + "{ (t: AvlTree) => t.keyLength }", + expectedExprFor("keyLength"))) + + testCases( + Seq( + (t1, Success(Some(1))), + (t2, Success(Some(64))), + (t3, Success(None)) + ), + existingFeature((t: AvlTree) => t.valueLengthOpt, + "{ (t: AvlTree) => t.valueLengthOpt }", + expectedExprFor("valueLengthOpt"))) + + testCases( + Seq( + (t1, Success(false)), + (t2, Success(false)), + (t3, Success(true)) + ), + existingFeature((t: AvlTree) => t.isInsertAllowed, + "{ (t: AvlTree) => t.isInsertAllowed }", + expectedExprFor("isInsertAllowed"))) + + testCases( + Seq( + (t1, Success(true)), + (t2, Success(false)), + (t3, Success(false)) + ), + existingFeature((t: AvlTree) => t.isUpdateAllowed, + "{ (t: AvlTree) => t.isUpdateAllowed }", + expectedExprFor("isUpdateAllowed"))) + + testCases( + Seq( + (t1, Success(true)), + (t2, Success(false)), + (t3, Success(false)) + ), + existingFeature((t: AvlTree) => t.isRemoveAllowed, + "{ (t: AvlTree) => t.isRemoveAllowed }", + expectedExprFor("isRemoveAllowed"))) + } + + property("AvlTree.{contains, get, getMany, updateDigest, updateOperations} equivalence") { + val contains = existingFeature( + (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.contains(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.contains(t._2._1, t._2._2) }", + FuncValue( + Vector( + (1, STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))))) + ), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte))))) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SBoolean.type]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse( + 1, + STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte))))) + ), + 1.toByte + ), + SAvlTree.getMethodByName("contains"), + Vector( + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))), + 2.toByte + ) + ), + Map() + ) + ) + )) + + val get = existingFeature((t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.get(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.get(t._2._1, t._2._2) }", + FuncValue( + Vector( + (1, STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))))) + ), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte))))) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SOption[SCollection[SByte.type]]]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse( + 1, + STuple(Vector(SAvlTree, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte))))) + ), + 1.toByte + ), + SAvlTree.getMethodByName("get"), + Vector( + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SCollectionType(SByte), SCollectionType(SByte)))), + 2.toByte + ) + ), + Map() + ) + ) + )) + + val getMany = existingFeature( + (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.getMany(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.getMany(t._2._1, t._2._2) }", + FuncValue( + Vector( + ( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(SCollectionType(SByte)), SCollectionType(SByte))) + ) + ) + ) + ), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(SCollectionType(SByte)), SCollectionType(SByte))) + ) + ) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SCollection[SOption[SCollection[SByte.type]]]]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(SCollectionType(SByte)), SCollectionType(SByte))) + ) + ) + ), + 1.toByte + ), + SAvlTree.getMethodByName("getMany"), + Vector( + SelectField.typed[Value[SCollection[SCollection[SByte.type]]]]( + ValUse(3, STuple(Vector(SCollectionType(SCollectionType(SByte)), SCollectionType(SByte)))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SCollectionType(SCollectionType(SByte)), SCollectionType(SByte)))), + 2.toByte + ) + ), + Map() + ) + ) + ) + ) + + val updateDigest = existingFeature((t: (AvlTree, Coll[Byte])) => t._1.updateDigest(t._2), + "{ (t: (AvlTree, Coll[Byte])) => t._1.updateDigest(t._2) }", + FuncValue( + Vector((1, STuple(Vector(SAvlTree, SCollectionType(SByte))))), + MethodCall.typed[Value[SAvlTree.type]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse(1, STuple(Vector(SAvlTree, SCollectionType(SByte)))), + 1.toByte + ), + SAvlTree.getMethodByName("updateDigest"), + Vector( + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(1, STuple(Vector(SAvlTree, SCollectionType(SByte)))), + 2.toByte + ) + ), + Map() + ) + )) + + val updateOperations = existingFeature((t: (AvlTree, Byte)) => t._1.updateOperations(t._2), + "{ (t: (AvlTree, Byte)) => t._1.updateOperations(t._2) }", + FuncValue( + Vector((1, STuple(Vector(SAvlTree, SByte)))), + MethodCall.typed[Value[SAvlTree.type]]( + SelectField.typed[Value[SAvlTree.type]](ValUse(1, STuple(Vector(SAvlTree, SByte))), 1.toByte), + SAvlTree.getMethodByName("updateOperations"), + Vector( + SelectField.typed[Value[SByte.type]](ValUse(1, STuple(Vector(SAvlTree, SByte))), 2.toByte) + ), + Map() + ) + )) + + val (key, value, _, avlProver) = sampleAvlProver + val otherKey = key.map(x => (-x).toByte) // any other different from key + + val table = Table(("key", "contains", "valueOpt"), (key, true, Some(value)), (otherKey, false, None)) + forAll(table) { (key, okContains, valueOpt) => + avlProver.performOneOperation(Lookup(ADKey @@ key.toArray)) + val proof = avlProver.generateProof().toColl + val digest = avlProver.digest.toColl + val tree = SigmaDsl.avlTree(AvlTreeFlags.ReadOnly.serializeToByte, digest, 32, None) + + // positive test + contains.checkExpected((tree, (key, proof)), okContains) + get.checkExpected((tree, (key, proof)), valueOpt) + + + val keys = Colls.fromItems(key) + val expRes = Colls.fromItems(valueOpt) + getMany.checkExpected((tree, (keys, proof)), expRes) + + updateDigest.checkEquality((tree, digest)).get.digest shouldBe digest + val newOps = 1.toByte + updateOperations.checkEquality((tree, newOps)).get.enabledOperations shouldBe newOps + + { // negative tests: invalid proof + val invalidProof = proof.map(x => (-x).toByte) // any other different from proof + val resContains = contains.checkEquality((tree, (key, invalidProof))) + resContains shouldBe Success(false) + val resGet = get.checkEquality((tree, (key, invalidProof))) + resGet.isFailure shouldBe true + val resGetMany = getMany.checkEquality((tree, (keys, invalidProof))) + resGetMany.isFailure shouldBe true + } + } + } + + type BatchProver = BatchAVLProver[Digest32, Blake2b256.type] + + def performInsert(avlProver: BatchProver, key: Coll[Byte], value: Coll[Byte]) = { + avlProver.performOneOperation(Insert(ADKey @@ key.toArray, ADValue @@ value.toArray)) + val proof = avlProver.generateProof().toColl + proof + } + + def performUpdate(avlProver: BatchProver, key: Coll[Byte], value: Coll[Byte]) = { + avlProver.performOneOperation(Update(ADKey @@ key.toArray, ADValue @@ value.toArray)) + val proof = avlProver.generateProof().toColl + proof + } + + def performRemove(avlProver: BatchProver, key: Coll[Byte]) = { + avlProver.performOneOperation(Remove(ADKey @@ key.toArray)) + val proof = avlProver.generateProof().toColl + proof + } + + def createTree(digest: Coll[Byte], insertAllowed: Boolean = false, updateAllowed: Boolean = false, removeAllowed: Boolean = false) = { + val flags = AvlTreeFlags(insertAllowed, updateAllowed, removeAllowed).serializeToByte + val tree = SigmaDsl.avlTree(flags, digest, 32, None) + tree + } + + type KV = (Coll[Byte], Coll[Byte]) + + property("AvlTree.insert equivalence") { + val insert = existingFeature((t: (AvlTree, (Coll[KV], Coll[Byte]))) => t._1.insert(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[(Coll[Byte], Coll[Byte])], Coll[Byte]))) => t._1.insert(t._2._1, t._2._2) }", + FuncValue( + Vector( + ( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ) + ), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SOption[SAvlTree.type]]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ), + 1.toByte + ), + SAvlTree.getMethodByName("insert"), + Vector( + SelectField.typed[Value[SCollection[STuple]]]( + ValUse( + 3, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse( + 3, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ), + 2.toByte + ) + ), + Map() + ) + ) + )) + + forAll(keyCollGen, bytesCollGen) { (key, value) => + val (tree, avlProver) = createAvlTreeAndProver() + val preInsertDigest = avlProver.digest.toColl + val insertProof = performInsert(avlProver, key, value) + val kvs = Colls.fromItems((key -> value)) + + { // positive + val preInsertTree = createTree(preInsertDigest, insertAllowed = true) + val res = insert.checkEquality((preInsertTree, (kvs, insertProof))) + res.get.isDefined shouldBe true + } + + { // negative: readonly tree + val readonlyTree = createTree(preInsertDigest) + val res = insert.checkEquality((readonlyTree, (kvs, insertProof))) + res.get.isDefined shouldBe false + } + + { // negative: invalid key + val tree = createTree(preInsertDigest, insertAllowed = true) + val invalidKey = key.map(x => (-x).toByte) // any other different from key + val invalidKvs = Colls.fromItems((invalidKey -> value)) // NOTE, insertProof is based on `key` + val res = insert.checkEquality((tree, (invalidKvs, insertProof))) + res.get.isDefined shouldBe true // TODO HF: should it really be true? (looks like a bug) + } + + { // negative: invalid proof + val tree = createTree(preInsertDigest, insertAllowed = true) + val invalidProof = insertProof.map(x => (-x).toByte) // any other different from proof + val res = insert.checkEquality((tree, (kvs, invalidProof))) + res.isFailure shouldBe true + } + } + } + + property("AvlTree.update equivalence") { + val update = existingFeature((t: (AvlTree, (Coll[KV], Coll[Byte]))) => t._1.update(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[(Coll[Byte], Coll[Byte])], Coll[Byte]))) => t._1.update(t._2._1, t._2._2) }", + FuncValue( + Vector( + ( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ) + ), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SOption[SAvlTree.type]]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse( + 1, + STuple( + Vector( + SAvlTree, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ) + ) + ), + 1.toByte + ), + SAvlTree.getMethodByName("update"), + Vector( + SelectField.typed[Value[SCollection[STuple]]]( + ValUse( + 3, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse( + 3, + STuple(Vector(SCollectionType(STuple(Vector(SByteArray, SByteArray))), SByteArray)) + ), + 2.toByte + ) + ), + Map() + ) + ) + )) + + forAll(keyCollGen, bytesCollGen) { (key, value) => + val (_, avlProver) = createAvlTreeAndProver(key -> value) + val preUpdateDigest = avlProver.digest.toColl + val newValue = bytesCollGen.sample.get + val updateProof = performUpdate(avlProver, key, newValue) + val kvs = Colls.fromItems((key -> newValue)) + val endDigest = avlProver.digest.toColl + + { // positive: update to newValue + val preUpdateTree = createTree(preUpdateDigest, updateAllowed = true) + val endTree = preUpdateTree.updateDigest(endDigest) + update.checkExpected((preUpdateTree, (kvs, updateProof)), Some(endTree)) + } + + { // positive: update to the same value (identity operation) + val tree = createTree(preUpdateDigest, updateAllowed = true) + val keys = Colls.fromItems((key -> value)) + update.checkExpected((tree, (keys, updateProof)), Some(tree)) + } + + { // negative: readonly tree + val readonlyTree = createTree(preUpdateDigest) + val res = update.checkExpected((readonlyTree, (kvs, updateProof)), None) + } + + { // negative: invalid key + val tree = createTree(preUpdateDigest, updateAllowed = true) + val invalidKey = key.map(x => (-x).toByte) // any other different from key + val invalidKvs = Colls.fromItems((invalidKey -> newValue)) + update.checkExpected((tree, (invalidKvs, updateProof)), None) + } + + { // negative: invalid value (different from the value in the proof) + val tree = createTree(preUpdateDigest, updateAllowed = true) + val invalidValue = newValue.map(x => (-x).toByte) + val invalidKvs = Colls.fromItems((key -> invalidValue)) + val res = update.checkEquality((tree, (invalidKvs, updateProof))) + res.get.isDefined shouldBe true // TODO HF: should it really be true? (looks like a bug) + } + + { // negative: invalid proof + val tree = createTree(preUpdateDigest, updateAllowed = true) + val invalidProof = updateProof.map(x => (-x).toByte) // any other different from proof + update.checkExpected((tree, (kvs, invalidProof)), None) + } + } + } + + property("AvlTree.remove equivalence") { + val remove = existingFeature((t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.remove(t._2._1, t._2._2), + "{ (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.remove(t._2._1, t._2._2) }", + FuncValue( + Vector((1, STuple(Vector(SAvlTree, STuple(Vector(SByteArray2, SByteArray)))))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse(1, STuple(Vector(SAvlTree, STuple(Vector(SByteArray2, SByteArray))))), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SOption[SAvlTree.type]]]( + SelectField.typed[Value[SAvlTree.type]]( + ValUse(1, STuple(Vector(SAvlTree, STuple(Vector(SByteArray2, SByteArray))))), + 1.toByte + ), + SAvlTree.getMethodByName("remove"), + Vector( + SelectField.typed[Value[SCollection[SCollection[SByte.type]]]]( + ValUse(3, STuple(Vector(SByteArray2, SByteArray))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(3, STuple(Vector(SByteArray2, SByteArray))), + 2.toByte + ) + ), + Map() + ) + ) + )) + + forAll(keyCollGen, bytesCollGen) { (key, value) => + val (_, avlProver) = createAvlTreeAndProver(key -> value) + val preRemoveDigest = avlProver.digest.toColl + val removeProof = performRemove(avlProver, key) + val endDigest = avlProver.digest.toColl + val keys = Colls.fromItems(key) + + { // positive + val preRemoveTree = createTree(preRemoveDigest, removeAllowed = true) + val endTree = preRemoveTree.updateDigest(endDigest) + remove.checkExpected((preRemoveTree, (keys, removeProof)), Some(endTree)) + } + + { // negative: readonly tree + val readonlyTree = createTree(preRemoveDigest) + remove.checkExpected((readonlyTree, (keys, removeProof)), None) + } + + { // negative: invalid key + val tree = createTree(preRemoveDigest, removeAllowed = true) + val invalidKey = key.map(x => (-x).toByte) // any other different from `key` + val invalidKeys = Colls.fromItems(invalidKey) + remove.checkExpected((tree, (invalidKeys, removeProof)), None) + } + + { // negative: invalid proof + val tree = createTree(preRemoveDigest, removeAllowed = true) + val invalidProof = removeProof.map(x => (-x).toByte) // any other different from `removeProof` + remove.checkExpected((tree, (keys, invalidProof)), None) + } + } + } + + property("longToByteArray equivalence") { + testCases( + Seq( + (-9223372036854775808L, Success(Helpers.decodeBytes("8000000000000000"))), + (-1148502660425090565L, Success(Helpers.decodeBytes("f00fb2ea55c579fb"))), + (-1L, Success(Helpers.decodeBytes("ffffffffffffffff"))), + (0L, Success(Helpers.decodeBytes("0000000000000000"))), + (1L, Success(Helpers.decodeBytes("0000000000000001"))), + (238790047448232028L, Success(Helpers.decodeBytes("03505a48720cf05c"))), + (9223372036854775807L, Success(Helpers.decodeBytes("7fffffffffffffff"))) + ), + existingFeature((x: Long) => SigmaDsl.longToByteArray(x), + "{ (x: Long) => longToByteArray(x) }", + FuncValue(Vector((1, SLong)), LongToByteArray(ValUse(1, SLong))))) + } + + property("byteArrayToBigInt equivalence") { + testCases( + Seq( + (Helpers.decodeBytes(""), + Failure(new NumberFormatException("Zero length BigInteger"))), + (Helpers.decodeBytes("00"), + Success(CBigInt(new BigInteger("0", 16)))), + (Helpers.decodeBytes("01"), + Success(CBigInt(new BigInteger("1", 16)))), + (Helpers.decodeBytes("ff"), + Success(CBigInt(new BigInteger("-1", 16)))), + (Helpers.decodeBytes("80d6c201"), + Success(CBigInt(new BigInteger("-7f293dff", 16)))), + (Helpers.decodeBytes("70d6c201"), + Success(CBigInt(new BigInteger("70d6c201", 16)))), + (Helpers.decodeBytes( + "80e0ff7f02807fff72807f0a00ff7fb7c57f75c11ba2802970fd250052807fc37f6480ffff007fff18eeba44" + ), Failure(new ArithmeticException("BigInteger out of 256 bit range"))) + ), + existingFeature((x: Coll[Byte]) => SigmaDsl.byteArrayToBigInt(x), + "{ (x: Coll[Byte]) => byteArrayToBigInt(x) }", + FuncValue(Vector((1, SByteArray)), ByteArrayToBigInt(ValUse(1, SByteArray))))) + } + + property("byteArrayToLong equivalence") { + testCases( + Seq( + (Helpers.decodeBytes(""), Failure(new IllegalArgumentException("array too small: 0 < 8"))), + (Helpers.decodeBytes("81"), Failure(new IllegalArgumentException("array too small: 1 < 8"))), + (Helpers.decodeBytes("812d7f00ff807f"), Failure(new IllegalArgumentException("array too small: 7 < 8"))), + (Helpers.decodeBytes("812d7f00ff807f7f"), Success(-9138508426601529473L)), + (Helpers.decodeBytes("ffffffffffffffff"), Success(-1L)), + (Helpers.decodeBytes("0000000000000000"), Success(0L)), + (Helpers.decodeBytes("0000000000000001"), Success(1L)), + (Helpers.decodeBytes("712d7f00ff807f7f"), Success(8155314142501175167L)), + (Helpers.decodeBytes("812d7f00ff807f7f0101018050757f0580ac009680f2ffc1"), Success(-9138508426601529473L)) + ), + existingFeature((x: Coll[Byte]) => SigmaDsl.byteArrayToLong(x), + "{ (x: Coll[Byte]) => byteArrayToLong(x) }", + FuncValue(Vector((1, SByteArray)), ByteArrayToLong(ValUse(1, SByteArray))))) + } + + // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/427 + // TODO costing: expression t._1(t._2) cannot be costed because t is lambda argument + // ignore("Func context variable") { + // val doApply = checkEq(func[(Int => Int, Int), Int]("{ (t: (Int => Int, Int)) => t._1(t._2) }")) { (t: (Int => Int, Int)) => t._1(t._2) } + // val code = compileWithCosting(emptyEnv, s"{ (x: Int) => x + 1 }") + // val ctx = ErgoLikeContext.dummy(fakeSelf) + // doApply((CFunc[Int, Int](ctx, code), 10)) + // } + + lazy val ctx = ergoCtx.toSigmaContext(IR, false) + + property("Box properties equivalence") { + val b1 = CostingBox( + false, + new ErgoBox( + 9223372036854775807L, + new ErgoTree( + 16.toByte, + Array( + SigmaPropConstant( + CSigmaProp( + ProveDlog( + Helpers.decodeECPoint( + "0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e" + ) + ) + ) + ) + ), + Right(ConstantPlaceholder(0, SSigmaProp)) + ), + Coll( + (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), + 10000000L), + (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), + 500L) + ), + Map( + ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), + ErgoBox.R4 -> FalseLeaf + ), + ModifierId @@ ("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080"), + 22588.toShort, + 677407 + ) + ) + + val b2 = CostingBox( + false, + new ErgoBox( + 12345L, + new ErgoTree( + 0.toByte, + Vector(), + Right( + BoolToSigmaProp( + AND( + ConcreteCollection( + Array( + FalseLeaf, + XorOf( + ConcreteCollection(Array(EQ(IntConstant(1), IntConstant(1)), FalseLeaf), SBoolean) + ) + ), + SBoolean + ) + ) + ) + ) + ), + Coll(), + Map( + ErgoBox.R5 -> ByteArrayConstant( + Helpers.decodeBytes( + "297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d65" + ) + ), + ErgoBox.R4 -> TrueLeaf, + ErgoBox.R7 -> LongConstant(9223372036854775807L), + ErgoBox.R6 -> LongConstant(2115927197107005906L) + ), + ModifierId @@ ("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e"), + 1.toShort, + 1000000 + ) + ) + + testCases( + Seq( + (b1, Success(Helpers.decodeBytes("5ee78f30ae4e770e44900a46854e9fecb6b12e8112556ef1cd19aef633b4421e"))), + (b2, Success(Helpers.decodeBytes("3a0089be265460e29ca47d26e5b55a6f3e3ffaf5b4aed941410a2437913848ad"))) + ), + existingFeature({ (x: Box) => x.id }, + "{ (x: Box) => x.id }", + FuncValue(Vector((1, SBox)), ExtractId(ValUse(1, SBox))))) + + testCases( + Seq( + (b1, Success(9223372036854775807L)), + (b2, Success(12345L)) + ), + existingFeature({ (x: Box) => x.value }, + "{ (x: Box) => x.value }", + FuncValue(Vector((1, SBox)), ExtractAmount(ValUse(1, SBox))))) + + testCases( + Seq( + (b1, Success(Helpers.decodeBytes( + "100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e7300" + ))), + (b2, Success(Helpers.decodeBytes("00d1968302010100ff83020193040204020100"))) + ), + existingFeature({ (x: Box) => x.propositionBytes }, + "{ (x: Box) => x.propositionBytes }", + FuncValue(Vector((1, SBox)), ExtractScriptBytes(ValUse(1, SBox))))) + + testCases( + Seq( + (b1, Success(Helpers.decodeBytes( + "ffffffffffffffff7f100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e73009fac29026e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f000180ade204a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600f4030201000e067fc87f7f01ff218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080bcb001" + ))), + (b2, Success(Helpers.decodeBytes( + "b96000d1968302010100ff83020193040204020100c0843d000401010e32297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d6505a4a7b5a2e7a4a4dd3a05feffffffffffffffff01003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e01" + ))) + ), + existingFeature({ (x: Box) => x.bytes }, + "{ (x: Box) => x.bytes }", + FuncValue(Vector((1, SBox)), ExtractBytes(ValUse(1, SBox))))) + + testCases( + Seq( + (b1, Success(Helpers.decodeBytes( + "ffffffffffffffff7f100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e73009fac29026e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f000180ade204a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600f4030201000e067fc87f7f01ff" + ))), + (b2, Success(Helpers.decodeBytes( + "b96000d1968302010100ff83020193040204020100c0843d000401010e32297000800b80f1d56c809a8c6affbed864b87f007f6f007f00ac00018c01c4fdff011088807f0100657f00f9ab0101ff6d6505a4a7b5a2e7a4a4dd3a05feffffffffffffffff01" + ))) + ), + existingFeature({ (x: Box) => x.bytesWithoutRef }, + "{ (x: Box) => x.bytesWithoutRef }", + FuncValue(Vector((1, SBox)), ExtractBytesWithNoRef(ValUse(1, SBox))))) + + testCases( + Seq( + (b1, Success(( + 677407, + Helpers.decodeBytes("218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080583c") + ))), + (b2, Success(( + 1000000, + Helpers.decodeBytes("003bd5c630803cfff6c1ff7f7fb980ff136afc011f8080b8b04ad4dbda2d7f4e0001") + ))) + ), + existingFeature({ (x: Box) => x.creationInfo }, + "{ (x: Box) => x.creationInfo }", + FuncValue(Vector((1, SBox)), ExtractCreationInfo(ValUse(1, SBox))))) + + // TODO HF: fix collections equality and remove map(identity) + // (PairOfColl should be equal CollOverArray) + testCases( + Seq( + (b1, Success(Coll[(Coll[Byte], Long)]( + (Helpers.decodeBytes("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001"), 10000000L), + (Helpers.decodeBytes("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600"), 500L) + ).map(identity)) + ), + (b2, Success(Coll[(Coll[Byte], Long)]().map(identity))) + ), + existingFeature({ (x: Box) => x.tokens }, + "{ (x: Box) => x.tokens }", + FuncValue( + Vector((1, SBox)), + MethodCall.typed[Value[SCollection[STuple]]]( + ValUse(1, SBox), + SBox.getMethodByName("tokens"), + Vector(), + Map() + ) + ))) + + } + + property("Box properties equivalence (new features)") { + // TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 + val getReg = newFeature((x: Box) => x.getReg[Int](1).get, + "{ (x: Box) => x.getReg[Int](1).get }") + + forAll { box: Box => + Seq(getReg).foreach(_.checkEquality(box)) + } + } + + property("Advanced Box test") { + val (tree, _) = createAvlTreeAndProver() + + val box1 = SigmaDsl.Box(ErgoBox(20, TrueProp, 0, Seq(), Map( + ErgoBox.R4 -> ByteConstant(1.toByte), + ErgoBox.R5 -> ShortConstant(1024.toShort), + ErgoBox.R6 -> IntConstant(1024 * 1024), + ErgoBox.R7 -> LongConstant(1024.toLong), + ErgoBox.R8 -> BigIntConstant(222L), + ErgoBox.R9 -> AvlTreeConstant(tree) + ))) + + val box2 = SigmaDsl.Box(ErgoBox(20, TrueProp, 0, Seq(), Map( + ErgoBox.R4 -> ByteArrayConstant(Coll(1.toByte)) + ))) + + testCases( + Seq( + (box1, Success(1.toByte)), + (box2, Failure(new InvalidType("Cannot getReg[Byte](4): invalid type of value Value(Coll(1)) at id=4"))) + ), + existingFeature((x: Box) => x.R4[Byte].get, + "{ (x: Box) => x.R4[Byte].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R4, SOption(SByte))) + ))) + + testCases( + Seq( + (box1, Success(1024.toShort)), + (box2, Failure(new NoSuchElementException("None.get"))) + ), + existingFeature((x: Box) => x.R5[Short].get, + "{ (x: Box) => x.R5[Short].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R5, SOption(SShort))) + ))) + + testCases( + Seq( + (box1, Success(1024 * 1024)) + ), + existingFeature((x: Box) => x.R6[Int].get, + "{ (x: Box) => x.R6[Int].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R6, SOption(SInt))) + ))) + + testCases( + Seq( + (box1, Success(1024.toLong)) + ), + existingFeature((x: Box) => x.R7[Long].get, + "{ (x: Box) => x.R7[Long].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R7, SOption(SLong))) + ))) + + testCases( + Seq( + (box1, Success(CBigInt(BigInteger.valueOf(222L)))) + ), + existingFeature((x: Box) => x.R8[BigInt].get, + "{ (x: Box) => x.R8[BigInt].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R8, SOption(SBigInt))) + ))) + + testCases( + Seq( + (box1, Success(CAvlTree( + AvlTreeData( + ADDigest @@ ( + ErgoAlgos.decodeUnsafe("4ec61f485b98eb87153f7c57db4f5ecd75556fddbc403b41acf8441fde8e160900") + ), + AvlTreeFlags(true, true, true), + 32, + None + ) + ))) + ), + existingFeature((x: Box) => x.R9[AvlTree].get, + "{ (x: Box) => x.R9[AvlTree].get }", + FuncValue( + Vector((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R9, SOption(SAvlTree))) + ))) + } + + def existingPropTest[A: RType, B: RType](propName: String, scalaFunc: A => B) = { + val tA = RType[A] + val typeName = tA.name + val stypeA = Evaluation.rtypeToSType(tA) + val typeCompanion = stypeA.asInstanceOf[STypeCompanion] + existingFeature(scalaFunc, + s"{ (x: $typeName) => x.$propName }", + FuncValue(Vector((1, stypeA)), + MethodCall(ValUse(1, stypeA), typeCompanion.getMethodByName(propName), Vector(), Map() ) + )) + } + + property("PreHeader properties equivalence") { + val h1 = CPreHeader( + 0.toByte, + Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), + 6306290372572472443L, + -3683306095029417063L, + 1, + Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), + Helpers.decodeBytes("ff8087") + ) + + testCases( + Seq((h1, Success(0.toByte))), + existingPropTest("version", { (x: PreHeader) => x.version })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40")))), + existingPropTest("parentId", { (x: PreHeader) => x.parentId })) + + testCases( + Seq((h1, Success(6306290372572472443L))), + existingPropTest("timestamp", { (x: PreHeader) => x.timestamp })) + + testCases( + Seq((h1, Success(-3683306095029417063L))), + existingPropTest("nBits", { (x: PreHeader) => x.nBits })) + + testCases( + Seq((h1, Success(1))), + existingPropTest("height", { (x: PreHeader) => x.height })) + + testCases( + Seq((h1, Success(Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b")))), + existingPropTest("minerPk", { (x: PreHeader) => x.minerPk })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("ff8087")))), + existingPropTest("votes", { (x: PreHeader) => x.votes })) + } + + property("Header properties equivalence") { + val treeData = AvlTreeData( + ADDigest @@ ( + ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080") + ), + AvlTreeFlags(false, true, false), + 728138553, + Some(2147483647) + ) + val h1 = CHeader( + Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), + 0.toByte, + Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), + Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), + CAvlTree(treeData), + Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), + 1L, + -1L, + 1, + Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), + Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), + Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), + Helpers.decodeBytes("7f4f09012a807f01"), + CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), + Helpers.decodeBytes("7f0180") + ) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a")))), + existingPropTest("id", { (x: Header) => x.id })) + + testCases( + Seq((h1, Success(0.toByte))), + existingPropTest("version", { (x: Header) => x.version })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff")))), + existingPropTest("parentId", { (x: Header) => x.parentId })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d")))), + existingPropTest("ADProofsRoot", { (x: Header) => x.ADProofsRoot})) + + testCases( + Seq((h1, Success(CAvlTree(treeData)))), + existingPropTest("stateRoot", { (x: Header) => x.stateRoot })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100")))), + existingPropTest("transactionsRoot", { (x: Header) => x.transactionsRoot })) + + testCases( + Seq((h1, Success(1L))), + existingPropTest("timestamp", { (x: Header) => x.timestamp })) + + testCases( + Seq((h1, Success(-1L))), + existingPropTest("nBits", { (x: Header) => x.nBits })) + + testCases( + Seq((h1, Success(1))), + existingPropTest("height", { (x: Header) => x.height })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62")))), + existingPropTest("extensionRoot", { (x: Header) => x.extensionRoot })) + + testCases( + Seq((h1, Success(Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904")))), + existingPropTest("minerPk", { (x: Header) => x.minerPk })) + + testCases( + Seq((h1, Success(Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c")))), + existingPropTest("powOnetimePk", { (x: Header) => x.powOnetimePk })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("7f4f09012a807f01")))), + existingPropTest("powNonce", { (x: Header) => x.powNonce })) + + testCases( + Seq((h1, Success(CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16))))), + existingPropTest("powDistance", { (x: Header) => x.powDistance })) + + testCases( + Seq((h1, Success(Helpers.decodeBytes("7f0180")))), + existingPropTest("votes", { (x: Header) => x.votes })) + } + + property("Context properties equivalence") { + val samples = genSamples[Context](MinSuccessful(5)) + + val input = CostingBox( + false, + new ErgoBox( + 80946L, + new ErgoTree( + 16.toByte, + Vector( + SigmaPropConstant( + CSigmaProp( + ProveDHTuple( + Helpers.decodeECPoint("03c046fccb95549910767d0543f5e8ce41d66ae6a8720a46f4049cac3b3d26dafb"), + Helpers.decodeECPoint("023479c9c3b86a0d3c8be3db0a2d186788e9af1db76d55f3dad127d15185d83d03"), + Helpers.decodeECPoint("03d7898641cb6653585a8e1dabfa7f665e61e0498963e329e6e3744bd764db2d72"), + Helpers.decodeECPoint("037ae057d89ec0b46ff8e9ff4c37e85c12acddb611c3f636421bef1542c11b0441") + ) + ) + ) + ), + Right(ConstantPlaceholder(0, SSigmaProp)) + ), + Coll(), + Map( + ErgoBox.R4 -> ByteArrayConstant(Helpers.decodeBytes("34")), + ErgoBox.R5 -> TrueLeaf + ), + ModifierId @@ ("0000bfe96a7c0001e7a5ee00aafb80ff057fbe7f8c6680e33a3dc18001820100"), + 1.toShort, + 5 + ) + ) + + val dataBox = CostingBox( + false, + new ErgoBox( + -1L, + new ErgoTree( + 0.toByte, + Vector(), + Right(SigmaPropConstant(CSigmaProp(ProveDlog(Helpers.decodeECPoint("02af645874c3b53465a5e9d820eb207d6001258c3b708f0d31d7c2e342833dce64"))))) + ), + Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("8f0000ff009e7fff012427ff7fffcc35dfe680017f004ef3be1280e57fc40101")), 500L)), + Map( + ErgoBox.R9 -> LongConstant(-6985752043373238161L), + ErgoBox.R4 -> LongConstant(-7374898275229807247L), + ErgoBox.R6 -> ByteArrayConstant(Helpers.decodeBytes("00")), + ErgoBox.R5 -> LongConstant(-135729055492651903L), + ErgoBox.R8 -> TrueLeaf, + ErgoBox.R7 -> ByteArrayConstant( + Helpers.decodeBytes( + "5a017f1f9d2e01ff004f007f807f21b87f899e3380014900010c0101da80e9809d2a85ff010125cc017f74ed8c7f96b55efffadf7f7fffa700012e8085a915007f7f0001ffd5013e0180d58bb300c5b77f231e7f1c01013d807afd387f80287f80a51900" + ) + ) + ), + ModifierId @@ ("ff3f4e00d400ff00ffae3680927f782affc0004b9f0092ca98010080f60100c1"), + 9495.toShort, + 1000000 + ) + ) + + val header = CHeader( + Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), + 0.toByte, + Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), + Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), + CAvlTree( + AvlTreeData( + ADDigest @@ (ErgoAlgos.decodeUnsafe("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17")), + AvlTreeFlags(true, true, false), + 2147483647, + None + ) + ), + Helpers.decodeBytes("5e7f1164ccd0990080c501fc0e0181cb387fc17f00ff00c7d5ff767f91ff5e68"), + -7421721754642387858L, + -4826493284887861030L, + 10, + Helpers.decodeBytes("e580c88001ff6fc89c5501017f80e001ff0101fe48c153ff7f00666b80d780ab"), + Helpers.decodeGroupElement("03e7f2875298fddd933c2e0a38968fe85bdeeb70dd8b389559a1d36e2ff1b58fc5"), + Helpers.decodeGroupElement("034e2d3b5f9e409e3ae8a2e768340760362ca33764eda5855f7a43487f14883300"), + Helpers.decodeBytes("974651c9efff7f00"), + CBigInt(new BigInteger("478e827dfa1e4b57", 16)), + Helpers.decodeBytes("01ff13") + ) + + val ctx = CostingDataContext( + _dataInputs = Coll[Box](dataBox), + headers = Coll[Header](header), + preHeader = CPreHeader( + 0.toByte, + Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), + -755484979487531112L, + 9223372036854775807L, + 11, + Helpers.decodeGroupElement("0227a58e9b2537103338c237c52c1213bf44bdb344fa07d9df8ab826cca26ca08f"), + Helpers.decodeBytes("007f00") + ), + inputs = Coll[Box](input), + outputs = Coll[Box]( + CostingBox( + false, + new ErgoBox( + 1000000L, + new ErgoTree( + 16.toByte, + Vector( + SigmaPropConstant( + CSigmaProp( + COR( + List( + ProveDHTuple( + Helpers.decodeECPoint("021b4c0f54304b9c427d5c87846dd56a2fa361cd34a6cb8a2090aef043c9383198"), + Helpers.decodeECPoint("026826a4a9d0ec937c24d72da381ee6b5e74e49fb79a6a23a03fe0aa2cab3448ba"), + Helpers.decodeECPoint("02535153378ce30df1b31137680748de728f8512d29dfeeb1f331ac6a787cd00d8"), + Helpers.decodeECPoint("03d00d0174cdffd7ce3b77ef45ef9573c18fb76929fb3340f7ceea8d0be9bf5c4a") + ), + ProveDlog(Helpers.decodeECPoint("02c702d83f83a5ec9674e17e5eb3ab3ae579768c945590f0fb10c1c4a388353c7c")), + ProveDHTuple( + Helpers.decodeECPoint("03bef02fb10347eef473730711ec313b2f013322e6dad32515bd172249420f25e5"), + Helpers.decodeECPoint("0365160972ed72d232f0cb5fa7909ac1647eb122942b421493def6a6051005d141"), + Helpers.decodeECPoint("035060119f4b47ccf12c4502657e9ee38dba92fc6b6b1807b75d5cdc1986754751"), + Helpers.decodeECPoint("02db7a6c1b51847ce5b1ba8e8c89b4ea5e68c5667f430e8bbe075ff4ea2877233a") + ) + ) + ) + ) + ) + ), + Right(ConstantPlaceholder(0, SSigmaProp)) + ), + Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6")), 20000L)), + Map(ErgoBox.R5 -> LongConstant(1L), ErgoBox.R4 -> LongConstant(5008366408131208436L)), + ModifierId @@ ("26485d14a94ef18ec36227a838b98e11e910087be4c7e634f51391e4ea4d16ff"), + 0.toShort, + 11 + ) + ), + CostingBox( + false, + new ErgoBox( + 2769336982721999022L, + new ErgoTree( + 0.toByte, + Vector(), + Right(SigmaPropConstant(CSigmaProp(ProveDlog(Helpers.decodeECPoint("02d13e1a8c31f32194761adc1cdcbaa746b3e049e682bba9308d8ee84576172991"))))) + ), + Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6")), 500L)), + Map(), + ModifierId @@ ("26485d14a94ef18ec36227a838b98e11e910087be4c7e634f51391e4ea4d16ff"), + 1.toShort, + 0 + ) + ) + ), + height = 11, + selfBox = input.copy(), // TODO HF: in 3.x implementation selfBox is never the same instance as input (see toSigmaContext) + lastBlockUtxoRootHash = CAvlTree( + AvlTreeData( + ADDigest @@ (ErgoAlgos.decodeUnsafe("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17")), + AvlTreeFlags(true, true, true), + 1211925457, + None + ) + ), + _minerPubKey = Helpers.decodeBytes("0227a58e9b2537103338c237c52c1213bf44bdb344fa07d9df8ab826cca26ca08f"), + vars = Coll[AnyValue](null, TestValue(Helpers.decodeBytes("00"), CollType(RType.ByteType)), TestValue(true, RType.BooleanType)), + false + ) + + test(samples, existingPropTest("dataInputs", { (x: Context) => x.dataInputs })) + + testCases( + Seq( + (ctx, Success(dataBox)), + (ctx.copy(_dataInputs = Coll()), Failure(new ArrayIndexOutOfBoundsException("0"))) + ), + existingFeature({ (x: Context) => x.dataInputs(0) }, + "{ (x: Context) => x.dataInputs(0) }", + FuncValue( + Vector((1, SContext)), + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (ctx, Success(Helpers.decodeBytes("7da4b55971f19a78d007638464580f91a020ab468c0dbe608deb1f619e245bc3"))) + ), + existingFeature({ (x: Context) => x.dataInputs(0).id }, + "{ (x: Context) => x.dataInputs(0).id }", + FuncValue( + Vector((1, SContext)), + ExtractId( + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ) + ) + )), + preGeneratedSamples = Some(samples)) + + test(samples, existingPropTest("preHeader", { (x: Context) => x.preHeader })) + + test(samples, existingPropTest("headers", { (x: Context) => x.headers })) + + test(samples, existingFeature({ (x: Context) => x.OUTPUTS }, + "{ (x: Context) => x.OUTPUTS }", FuncValue(Vector((1, SContext)), Outputs))) + + test(samples, existingFeature({ (x: Context) => x.INPUTS }, + "{ (x: Context) => x.INPUTS }", FuncValue(Vector((1, SContext)), Inputs))) + + test(samples, existingFeature({ (x: Context) => x.HEIGHT }, + "{ (x: Context) => x.HEIGHT }", FuncValue(Vector((1, SContext)), Height))) + + test(samples, existingFeature({ (x: Context) => x.SELF }, + "{ (x: Context) => x.SELF }", FuncValue(Vector((1, SContext)), Self))) + + testCases( + Seq((ctx, Success(Coll[Long](80946L)))), + existingFeature( + { (x: Context) => x.INPUTS.map { (b: Box) => b.value } }, + "{ (x: Context) => x.INPUTS.map { (b: Box) => b.value } }", + FuncValue( + Vector((1, SContext)), + MapCollection(Inputs, FuncValue(Vector((3, SBox)), ExtractAmount(ValUse(3, SBox)))) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq((ctx, Success(Coll((80946L, 80946L))))), + existingFeature( + { (x: Context) => x.INPUTS.map { (b: Box) => (b.value, b.value) } }, + """{ (x: Context) => + | x.INPUTS.map { (b: Box) => (b.value, b.value) } + |}""".stripMargin, + FuncValue( + Vector((1, SContext)), + MapCollection( + Inputs, + FuncValue( + Vector((3, SBox)), + BlockValue( + Vector(ValDef(5, List(), ExtractAmount(ValUse(3, SBox)))), + Tuple(Vector(ValUse(5, SLong), ValUse(5, SLong))) + ) + ) + ) + )), + preGeneratedSamples = Some(samples)) + + + testCases( + Seq((ctx, Failure(new InvalidType("Cannot getReg[Int](4): invalid type of value Value(Coll(52)) at id=4")))), + existingFeature( + { (x: Context) => + x.INPUTS.map { (b: Box) => + val pk = b.R4[Int].get + val value = longToByteArray(b.value) + (pk, value) + } + }, + """{ (x: Context) => + | x.INPUTS.map { (b: Box) => + | val pk = b.R4[Int].get + | val value = longToByteArray(b.value) + | (pk, value) + | } + |}""".stripMargin, + FuncValue( + Vector((1, SContext)), + MapCollection( + Inputs, + FuncValue( + Vector((3, SBox)), + Tuple( + Vector( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SInt))), + LongToByteArray(ExtractAmount(ValUse(3, SBox))) + ) + ) + ) + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq((ctx, Success(-1))), + existingFeature({ (x: Context) => x.selfBoxIndex }, + "{ (x: Context) => x.selfBoxIndex }", + FuncValue( + Vector((1, SContext)), + MethodCall.typed[Value[SInt.type]]( + ValUse(1, SContext), + SContext.getMethodByName("selfBoxIndex"), + Vector(), + Map() + ) + )), + preGeneratedSamples = Some(samples)) + + // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/603 + samples.foreach { c => + ctx.selfBoxIndex shouldBe -1 + } + + test(samples, + existingPropTest("LastBlockUtxoRootHash", { (x: Context) => x.LastBlockUtxoRootHash })) + + test(samples, existingFeature( + { (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }, + "{ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }", + FuncValue( + Vector((1, SContext)), + MethodCall.typed[Value[SBoolean.type]]( + MethodCall.typed[Value[SAvlTree.type]]( + ValUse(1, SContext), + SContext.getMethodByName("LastBlockUtxoRootHash"), + Vector(), + Map() + ), + SAvlTree.getMethodByName("isUpdateAllowed"), + Vector(), + Map() + ) + ))) + + test(samples, existingPropTest("minerPubKey", { (x: Context) => x.minerPubKey })) + + testCases( + Seq((ctx, Failure(new InvalidType("Cannot getVar[Int](2): invalid type of value Value(true) at id=2")))), + existingFeature((x: Context) => x.getVar[Int](2).get, + "{ (x: Context) => getVar[Int](2).get }", + FuncValue(Vector((1, SContext)), OptionGet(GetVar(2.toByte, SOption(SInt))))), + preGeneratedSamples = Some(samples)) + + testCases( + Seq((ctx, Success(true))), + existingFeature((x: Context) => x.getVar[Boolean](2).get, + "{ (x: Context) => getVar[Boolean](2).get }", + FuncValue(Vector((1, SContext)), OptionGet(GetVar(2.toByte, SOption(SBoolean))))), + preGeneratedSamples = Some(samples)) + } + + property("xorOf equivalence") { + // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 + testCases( + Seq( + (Coll[Boolean](false), Success(false)), + (Coll[Boolean](true), Success(false)), + (Coll[Boolean](false, false), Success(false)), + (Coll[Boolean](false, true), Success(true)), + (Coll[Boolean](true, false), Success(true)), + (Coll[Boolean](true, true), Success(false)), + (Coll[Boolean](false, false, false), Success(false)), + (Coll[Boolean](false, false, true), Success(true)), + (Coll[Boolean](false, true, false), Success(true)), + (Coll[Boolean](false, true, true), Success(true)), + (Coll[Boolean](true, false, false), Success(true)), + (Coll[Boolean](true, false, true), Success(true)), + (Coll[Boolean](true, true, false), Success(true)), + (Coll[Boolean](true, true, true), Success(false)), + (Coll[Boolean](false, false, false, false), Success(false)), + (Coll[Boolean](false, false, false, true), Success(true)), + (Coll[Boolean](false, false, true, false), Success(true)), + (Coll[Boolean](false, false, true, true), Success(true)) + ), + existingFeature((x: Coll[Boolean]) => SigmaDsl.xorOf(x), + "{ (x: Coll[Boolean]) => xorOf(x) }", + FuncValue(Vector((1, SBooleanArray)), XorOf(ValUse(1, SBooleanArray))))) + } + + property("LogicalNot equivalence") { + testCases( + Seq( + (true, Success(false)), + (false, Success(true))), + existingFeature((x: Boolean) => !x, + "{ (x: Boolean) => !x }", + FuncValue(Vector((1, SBoolean)), LogicalNot(ValUse(1, SBoolean)))), true) + } + + property("Numeric Negation equivalence") { + testCases( + Seq( + (Byte.MinValue, Success(Byte.MinValue)), // !!! + (-40.toByte, Success(40.toByte)), + (-1.toByte, Success(1.toByte)), + (0.toByte, Success(0.toByte)), + (1.toByte, Success(-1.toByte)), + (45.toByte, Success(-45.toByte)), + (127.toByte, Success(-127.toByte))), + existingFeature((x: Byte) => (-x).toByte, + "{ (x: Byte) => -x }", + FuncValue(Vector((1, SByte)), Negation(ValUse(1, SByte))))) + + testCases( + Seq( + (Short.MinValue, Success(Short.MinValue)), // special case! + ((Short.MinValue + 1).toShort, Success(32767.toShort)), + (-1528.toShort, Success(1528.toShort)), + (-1.toShort, Success(1.toShort)), + (0.toShort, Success(0.toShort)), + (1.toShort, Success(-1.toShort)), + (7586.toShort, Success(-7586.toShort)), + (Short.MaxValue, Success(-32767.toShort))), + existingFeature((x: Short) => (-x).toShort, + "{ (x: Short) => -x }", + FuncValue(Vector((1, SShort)), Negation(ValUse(1, SShort))))) + + testCases( + Seq( + (Int.MinValue, Success(Int.MinValue)), // special case! + (Int.MinValue + 1, Success(2147483647)), + (-63509744, Success(63509744)), + (-1, Success(1)), + (0, Success(0)), + (1, Success(-1)), + (677062351, Success(-677062351)), + (Int.MaxValue, Success(-2147483647))), + existingFeature((x: Int) => -x, + "{ (x: Int) => -x }", + FuncValue(Vector((1, SInt)), Negation(ValUse(1, SInt))))) + + testCases( + Seq( + (Long.MinValue, Success(Long.MinValue)), // special case! + (Long.MinValue + 1, Success(9223372036854775807L)), + (-957264171003115006L, Success(957264171003115006L)), + (-1L, Success(1L)), + (0L, Success(0L)), + (1L, Success(-1L)), + (340835904095777627L, Success(-340835904095777627L)), + (9223372036854775807L, Success(-9223372036854775807L))), + existingFeature((x: Long) => -x, + "{ (x: Long) => -x }", + FuncValue(Vector((1, SLong)), Negation(ValUse(1, SLong))))) + + testCases( + Seq( + (CBigInt(new BigInteger("-1655a05845a6ad363ac88ea21e88b97e436a1f02c548537e12e2d9667bf0680", 16)), Success(CBigInt(new BigInteger("1655a05845a6ad363ac88ea21e88b97e436a1f02c548537e12e2d9667bf0680", 16)))), + (CBigInt(new BigInteger("-1b24ba8badba8abf347cce054d9b9f14f229321507245b8", 16)), Success(CBigInt(new BigInteger("1b24ba8badba8abf347cce054d9b9f14f229321507245b8", 16)))), + (CBigInt(new BigInteger("-1ec9cca2c346cb72a1e65481eaa0627d", 16)), Success(CBigInt(new BigInteger("1ec9cca2c346cb72a1e65481eaa0627d", 16)))), + (CBigInt(new BigInteger("-8000000000000001", 16)), Success(CBigInt(new BigInteger("8000000000000001", 16)))), + (CBigInt(new BigInteger("-8000000000000000", 16)), Success(CBigInt(new BigInteger("8000000000000000", 16)))), + (CBigInt(new BigInteger("-48afe3e059821cd6", 16)), Success(CBigInt(new BigInteger("48afe3e059821cd6", 16)))), + (CBigInt(new BigInteger("-80000001", 16)), Success(CBigInt(new BigInteger("80000001", 16)))), + (CBigInt(new BigInteger("-80000000", 16)), Success(CBigInt(new BigInteger("80000000", 16)))), + (CBigInt(new BigInteger("-1", 16)), Success(CBigInt(new BigInteger("1", 16)))), + (CBigInt(new BigInteger("0", 16)), Success(CBigInt(new BigInteger("0", 16)))), + (CBigInt(new BigInteger("1", 16)), Success(CBigInt(new BigInteger("-1", 16)))), + (CBigInt(new BigInteger("7fffffff", 16)), Success(CBigInt(new BigInteger("-7fffffff", 16)))), + (CBigInt(new BigInteger("80000000", 16)), Success(CBigInt(new BigInteger("-80000000", 16)))), + (CBigInt(new BigInteger("90e8c3b6e8df65c", 16)), Success(CBigInt(new BigInteger("-90e8c3b6e8df65c", 16)))), + (CBigInt(new BigInteger("36aa93288257dcca141d0c01c5cef14c9d1c0f8507872e3fdd839a759636c78", 16)), Success(CBigInt(new BigInteger("-36aa93288257dcca141d0c01c5cef14c9d1c0f8507872e3fdd839a759636c78", 16))))), + existingFeature((x: BigInt) => x.negate(), + "{ (x: BigInt) => -x }", + FuncValue(Vector((1, SBigInt)), Negation(ValUse(1, SBigInt))))) + } + + property("global functions equivalence") { + + testCases( + Seq( + (-1, Success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (1, Success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")))), + existingFeature({ (x: Int) => SigmaDsl.groupGenerator }, + "{ (x: Int) => groupGenerator }", + FuncValue( + Vector((1, SInt)), + MethodCall.typed[Value[SGroupElement.type]]( + Global, + SGlobal.getMethodByName("groupGenerator"), + Vector(), + Map() + ) + ))) + + testCases( + Seq( + (-1, Success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (1, Success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")))), + existingFeature({ (x: Int) => SigmaDsl.groupGenerator }, + "{ (x: Int) => Global.groupGenerator }", + FuncValue( + Vector((1, SInt)), + MethodCall.typed[Value[SGroupElement.type]]( + Global, + SGlobal.getMethodByName("groupGenerator"), + Vector(), + Map() + ) + ))) + + testCases( + Seq( + (CBigInt(new BigInteger("-e5c1a54694c85d644fa30a6fc5f3aa209ed304d57f72683a0ebf21038b6a9d", 16)), Success(Helpers.decodeGroupElement("023395bcba3d7cf21d73c50f8af79d09a8c404c15ce9d04f067d672823bae91a54"))), + (CBigInt(new BigInteger("-bc2d08f935259e0eebf272c66c6e1dbd484c6706390215", 16)), Success(Helpers.decodeGroupElement("02ddcf4c48105faf3c16f7399b5dbedd82ab0bb50ae292d8f88f49a3f86e78974e"))), + (CBigInt(new BigInteger("-35cbe9a7a652e5fe85f735ee9909fdd8", 16)), Success(Helpers.decodeGroupElement("03b110ec9c7a8c20ed873818e976a0e96e5a17be979d3422d59b362de2a3ae043e"))), + (CBigInt(new BigInteger("-3f05ffca6bd4b15c", 16)), Success(Helpers.decodeGroupElement("02acf2657d0714cef8d65ae15c362faa09c0934c0bce872a23398e564c090b85c8"))), + (CBigInt(new BigInteger("-80000001", 16)), Success(Helpers.decodeGroupElement("0391b418fd1778356ce947a5cbb46539fd29842aea168486fae91fc5317177a575"))), + (CBigInt(new BigInteger("-80000000", 16)), Success(Helpers.decodeGroupElement("025318f9b1a2697010c5ac235e9af475a8c7e5419f33d47b18d33feeb329eb99a4"))), + (CBigInt(new BigInteger("-1", 16)), Success(Helpers.decodeGroupElement("0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (CBigInt(new BigInteger("0", 16)), Success(Helpers.decodeGroupElement("000000000000000000000000000000000000000000000000000000000000000000"))), + (CBigInt(new BigInteger("1", 16)), Success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), + (CBigInt(new BigInteger("80000000", 16)), Success(Helpers.decodeGroupElement("035318f9b1a2697010c5ac235e9af475a8c7e5419f33d47b18d33feeb329eb99a4"))), + (CBigInt(new BigInteger("1251b7fcd8a01e95", 16)), Success(Helpers.decodeGroupElement("030fde7238b8dddfafab8f5481dc17b880505d6bacbe3cdf2ce975afdcadf66354"))), + (CBigInt(new BigInteger("12f6bd76d8fe1d035bdb14bf2f696e52", 16)), Success(Helpers.decodeGroupElement("028f2ccf13669461cb3cfbea281e2db08fbb67b38493a1628855203d3f69b82763"))), + (CBigInt(new BigInteger("102bb404f5e36bdba004fdefa34df8cfa02e7912f3caf79", 16)), Success(Helpers.decodeGroupElement("03ce82f431d115d45ad555084f8b2861ce5c4561d154e931e9f778594896e46a25")))), + existingFeature({ (n: BigInt) => SigmaDsl.groupGenerator.exp(n) }, + "{ (n: BigInt) => groupGenerator.exp(n) }", + FuncValue( + Vector((1, SBigInt)), + Exponentiate( + MethodCall.typed[Value[SGroupElement.type]]( + Global, + SGlobal.getMethodByName("groupGenerator"), + Vector(), + Map() + ), + ValUse(1, SBigInt) + ) + ))) + + // TODO HF: fix semantics when the left collection is longer + testCases( + Seq( + ((Helpers.decodeBytes(""), Helpers.decodeBytes("")), Success(Helpers.decodeBytes(""))), + ((Helpers.decodeBytes("01"), Helpers.decodeBytes("01")), Success(Helpers.decodeBytes("00"))), + ((Helpers.decodeBytes("0100"), Helpers.decodeBytes("0101")), Success(Helpers.decodeBytes("0001"))), + ((Helpers.decodeBytes("01"), Helpers.decodeBytes("0101")), Success(Helpers.decodeBytes("00"))), + ((Helpers.decodeBytes("0100"), Helpers.decodeBytes("01")), Failure(new ArrayIndexOutOfBoundsException("1"))), + ((Helpers.decodeBytes("800136fe89afff802acea67128a0ff007fffe3498c8001806080012b"), + Helpers.decodeBytes("648018010a5d5800f5b400a580e7b4809b0cd273ff1230bfa800017f7fdb002749b3ac2b86ff")), + Success(Helpers.decodeBytes("e4812eff83f2a780df7aa6d4a8474b80e4f3313a7392313fc8800054"))) + ), + existingFeature((x: (Coll[Byte], Coll[Byte])) => SigmaDsl.xor(x._1, x._2), + "{ (x: (Coll[Byte], Coll[Byte])) => xor(x._1, x._2) }", + FuncValue( + Vector((1, STuple(Vector(SByteArray, SByteArray)))), + Xor( + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(1, STuple(Vector(SByteArray, SByteArray))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(1, STuple(Vector(SByteArray, SByteArray))), + 2.toByte + ) + ) + ))) + } + + property("Coll[Box] methods equivalence") { + val samples = genSamples[Coll[Box]](collOfN[Box](5), MinSuccessful(20)) + val b1 = CostingBox( + false, + new ErgoBox( + 1L, + new ErgoTree( + 0.toByte, + Vector(), + Right( + SigmaPropConstant( + CSigmaProp( + ProveDHTuple( + Helpers.decodeECPoint("02c1a9311ecf1e76c787ba4b1c0e10157b4f6d1e4db3ef0d84f411c99f2d4d2c5b"), + Helpers.decodeECPoint("027d1bd9a437e73726ceddecc162e5c85f79aee4798505bc826b8ad1813148e419"), + Helpers.decodeECPoint("0257cff6d06fe15d1004596eeb97a7f67755188501e36adc49bd807fe65e9d8281"), + Helpers.decodeECPoint("033c6021cff6ba5fdfc4f1742486030d2ebbffd9c9c09e488792f3102b2dcdabd5") + ) + ) + ) + ) + ), + Coll(), + Map( + ErgoBox.R4 -> ByteArrayConstant( + Helpers.decodeBytes( + "7200004cccdac3008001bc80ffc7ff9633bca3e501801380ff007900019d7f0001a8c9dfff5600d964011617ca00583f989c7f80007fee7f99b07f7f870067dc315180828080307fbdf400" + ) + ), + ErgoBox.R7 -> LongConstant(0L), + ErgoBox.R6 -> FalseLeaf, + ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7f")) + ), + ModifierId @@ ("7dffff48ab0000c101a2eac9ff17017f6180aa7fc6f2178000800179499380a5"), + 21591.toShort, + 638768 + ) + ) + val b2 = CostingBox( + false, + new ErgoBox( + 1000000000L, + new ErgoTree( + 0.toByte, + Vector(), + Right(BoolToSigmaProp(OR(ConcreteCollection(Array(FalseLeaf, AND(ConcreteCollection(Array(FalseLeaf, FalseLeaf), SBoolean))), SBoolean)))) + ), + Coll(), + Map(), + ModifierId @@ ("008677ffff7ff36dff00f68031140400007689ff014c9201ce8000a9ffe6ceff"), + 32767.toShort, + 32827 + ) + ) + + testCases( + Seq( + (Coll[Box](), Success(Coll[Box]())), + (Coll[Box](b1), Success(Coll[Box]())), + (Coll[Box](b1, b2), Success(Coll[Box](b2))) + ), + existingFeature({ (x: Coll[Box]) => x.filter({ (b: Box) => b.value > 1 }) }, + "{ (x: Coll[Box]) => x.filter({(b: Box) => b.value > 1 }) }", + FuncValue( + Vector((1, SCollectionType(SBox))), + Filter( + ValUse(1, SCollectionType(SBox)), + FuncValue(Vector((3, SBox)), GT(ExtractAmount(ValUse(3, SBox)), LongConstant(1L))) + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(Coll[Byte]())), + (Coll[Box](b1), Success(Helpers.decodeBytes( + "0008ce02c1a9311ecf1e76c787ba4b1c0e10157b4f6d1e4db3ef0d84f411c99f2d4d2c5b027d1bd9a437e73726ceddecc162e5c85f79aee4798505bc826b8ad1813148e4190257cff6d06fe15d1004596eeb97a7f67755188501e36adc49bd807fe65e9d8281033c6021cff6ba5fdfc4f1742486030d2ebbffd9c9c09e488792f3102b2dcdabd5" + ))), + (Coll[Box](b1, b2), Success(Helpers.decodeBytes( + "0008ce02c1a9311ecf1e76c787ba4b1c0e10157b4f6d1e4db3ef0d84f411c99f2d4d2c5b027d1bd9a437e73726ceddecc162e5c85f79aee4798505bc826b8ad1813148e4190257cff6d06fe15d1004596eeb97a7f67755188501e36adc49bd807fe65e9d8281033c6021cff6ba5fdfc4f1742486030d2ebbffd9c9c09e488792f3102b2dcdabd500d197830201010096850200" + ))) + ), + existingFeature({ (x: Coll[Box]) => x.flatMap({ (b: Box) => b.propositionBytes }) }, + "{ (x: Coll[Box]) => x.flatMap({(b: Box) => b.propositionBytes }) }", + FuncValue( + Vector((1, SCollectionType(SBox))), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.getMethodByName("flatMap").withConcreteTypes( + Map(STypeVar("IV") -> SBox, STypeVar("OV") -> SByte) + ), + Vector(FuncValue(Vector((3, SBox)), ExtractScriptBytes(ValUse(3, SBox)))), + Map() + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(Coll[(Box, Box)]())), + (Coll[Box](b1), Success(Coll[(Box, Box)]((b1, b1)))), + (Coll[Box](b1, b2), Success(Coll[(Box, Box)]((b1, b1), (b2, b2)))) + ), + existingFeature({ (x: Coll[Box]) => x.zip(x) }, + "{ (x: Coll[Box]) => x.zip(x) }", + FuncValue( + Vector((1, SCollectionType(SBox))), + MethodCall.typed[Value[SCollection[STuple]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.getMethodByName("zip").withConcreteTypes( + Map(STypeVar("IV") -> SBox, STypeVar("OV") -> SBox) + ), + Vector(ValUse(1, SCollectionType(SBox))), + Map() + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(0)), + (Coll[Box](b1), Success(1)), + (Coll[Box](b1, b2), Success(2)) + ), + existingFeature({ (x: Coll[Box]) => x.size }, + "{ (x: Coll[Box]) => x.size }", + FuncValue(Vector((1, SCollectionType(SBox))), SizeOf(ValUse(1, SCollectionType(SBox))))), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(Coll[Int]())), + (Coll[Box](b1), Success(Coll[Int](0))), + (Coll[Box](b1, b2), Success(Coll[Int](0, 1))) + ), + existingFeature({ (x: Coll[Box]) => x.indices }, + "{ (x: Coll[Box]) => x.indices }", + FuncValue( + Vector((1, SCollectionType(SBox))), + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SCollectionType(SBox)), + SCollection.getMethodByName("indices").withConcreteTypes(Map(STypeVar("IV") -> SBox)), + Vector(), + Map() + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(true)), + (Coll[Box](b1), Success(false)), + (Coll[Box](b1, b2), Success(false)) + ), + existingFeature({ (x: Coll[Box]) => x.forall({ (b: Box) => b.value > 1 }) }, + "{ (x: Coll[Box]) => x.forall({(b: Box) => b.value > 1 }) }", + FuncValue( + Vector((1, SCollectionType(SBox))), + ForAll( + ValUse(1, SCollectionType(SBox)), + FuncValue(Vector((3, SBox)), GT(ExtractAmount(ValUse(3, SBox)), LongConstant(1L))) + ) + )), + preGeneratedSamples = Some(samples)) + + testCases( + Seq( + (Coll[Box](), Success(false)), + (Coll[Box](b1), Success(false)), + (Coll[Box](b1, b2), Success(true)) + ), + existingFeature({ (x: Coll[Box]) => x.exists({ (b: Box) => b.value > 1 }) }, + "{ (x: Coll[Box]) => x.exists({(b: Box) => b.value > 1 }) }", + FuncValue( + Vector((1, SCollectionType(SBox))), + Exists( + ValUse(1, SCollectionType(SBox)), + FuncValue(Vector((3, SBox)), GT(ExtractAmount(ValUse(3, SBox)), LongConstant(1L))) + ) + )), + preGeneratedSamples = Some(samples)) + } + + val collWithRangeGen = for { + arr <- collGen[Int] + l <- Gen.choose(0, arr.length - 1) + r <- Gen.choose(l, arr.length - 1) } yield (arr, (l, r)) + + property("Coll patch method equivalence") { + val samples = genSamples(collWithRangeGen, MinSuccessful(50)) + testCases( + Seq( + ((Coll[Int](), (0, 0)), Success(Coll[Int]())), + ((Coll[Int](1), (0, 0)), Success(Coll[Int](1, 1))), + ((Coll[Int](1), (0, 1)), Success(Coll[Int](1))), + ((Coll[Int](1, 2), (0, 0)), Success(Coll[Int](1, 2, 1, 2))), + ((Coll[Int](1, 2), (1, 0)), Success(Coll[Int](1, 1, 2, 2))), + ((Coll[Int](1, 2), (0, 2)), Success(Coll[Int](1, 2))), + ((Coll[Int](1, 2), (0, 3)), Success(Coll[Int](1, 2))), + ((Coll[Int](1, 2), (1, 2)), Success(Coll[Int](1, 1, 2))), + ((Coll[Int](1, 2), (2, 0)), Success(Coll[Int](1, 2, 1, 2))), + ((Coll[Int](1, 2), (3, 0)), Success(Coll[Int](1, 2, 1, 2))), + ((Coll[Int](1, 2), (3, 1)), Success(Coll[Int](1, 2, 1, 2))), + ((Coll[Int](1, 2), (-1, 1)), Success(Coll[Int](1, 2, 2))) + ), + existingFeature( + { (x: (Coll[Int], (Int, Int))) => + val coll = x._1 + val l = x._2._1; val r = x._2._2 + coll.patch(l, coll, r) + }, + """{ (x: (Coll[Int], (Int, Int))) => + | val coll = x._1 + | val l = x._2._1; val r = x._2._2 + | coll.patch(l, coll, r) + |}""".stripMargin, + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SPair(SInt, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 1.toByte + ) + ), + ValDef( + 4, + List(), + SelectField.typed[Value[STuple]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(3, SCollectionType(SInt)), + SCollection.getMethodByName("patch").withConcreteTypes(Map(STypeVar("IV") -> SInt)), + Vector( + SelectField.typed[Value[SInt.type]](ValUse(4, SPair(SInt, SInt)), 1.toByte), + ValUse(3, SCollectionType(SInt)), + SelectField.typed[Value[SInt.type]](ValUse(4, SPair(SInt, SInt)), 2.toByte) + ), + Map() + ) + ) + )), + preGeneratedSamples = Some(samples)) + + } + + property("Coll updated method equivalence") { + testCases( + // (coll, (index, elem)) + Seq( + ((Coll[Int](), (0, 0)), Failure(new IndexOutOfBoundsException("0"))), + ((Coll[Int](1), (0, 0)), Success(Coll[Int](0))), + ((Coll[Int](1, 2), (0, 0)), Success(Coll[Int](0, 2))), + ((Coll[Int](1, 2), (1, 0)), Success(Coll[Int](1, 0))), + ((Coll[Int](1, 2, 3), (2, 0)), Success(Coll[Int](1, 2, 0))), + ((Coll[Int](1, 2), (2, 0)), Failure(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (3, 0)), Failure(new IndexOutOfBoundsException("3"))), + ((Coll[Int](1, 2), (-1, 0)), Failure(new IndexOutOfBoundsException("-1"))) + ), + existingFeature( + (x: (Coll[Int], (Int, Int))) => x._1.updated(x._2._1, x._2._2), + "{ (x: (Coll[Int], (Int, Int))) => x._1.updated(x._2._1, x._2._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SPair(SInt, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SCollection[SInt.type]]]( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 1.toByte + ), + SCollection.getMethodByName("updated").withConcreteTypes(Map(STypeVar("IV") -> SInt)), + Vector( + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 1.toByte), + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 2.toByte) + ), + Map() + ) + ) + ))) + } + + property("Coll updateMany method equivalence") { + val samples = genSamples( + for { + coll <- collGen[Int] + is <- genIndices(coll.length) + vs <- collOfN[Int](is.length) + } yield (coll, (is.toColl, vs)), + MinSuccessful(20)) + + testCases( + // (coll, (indexes, values)) + Seq( + ((Coll[Int](), (Coll(0), Coll(0))), Failure(new IndexOutOfBoundsException("0"))), + ((Coll[Int](), (Coll(0, 1), Coll(0, 0))), Failure(new IndexOutOfBoundsException("0"))), + ((Coll[Int](), (Coll(0, 1), Coll(0))), Failure(new IllegalArgumentException("requirement failed: Collections should have same length but was 2 and 1:\n xs=Coll(0,1);\n ys=Coll(0)"))), + ((Coll[Int](1), (Coll(0), Coll(0))), Success(Coll[Int](0))), + ((Coll[Int](1), (Coll(0, 1), Coll(0, 0))), Failure(new IndexOutOfBoundsException("1"))), + ((Coll[Int](1, 2), (Coll(0), Coll(0))), Success(Coll[Int](0, 2))), + ((Coll[Int](1, 2), (Coll(0, 1), Coll(0, 0))), Success(Coll[Int](0, 0))), + ((Coll[Int](1, 2), (Coll(0, 1, 2), Coll(0, 0, 0))), Failure(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (Coll(1), Coll(0))), Success(Coll[Int](1, 0))), + ((Coll[Int](1, 2, 3), (Coll(2), Coll(0))), Success(Coll[Int](1, 2, 0))), + ((Coll[Int](1, 2), (Coll(2), Coll(0))), Failure(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (Coll(3), Coll(0))), Failure(new IndexOutOfBoundsException("3"))), + ((Coll[Int](1, 2), (Coll(-1), Coll(0))), Failure(new IndexOutOfBoundsException("-1"))), + ((Coll[Int](10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140), + (Coll[Int](12, 12, 4, 11, 1, 8, 0, 1), Coll[Int](-10, -20, -30, -40, -50, -60, -70, -80))), + Success(Coll[Int](-70, -80, 30, 40, -30, 60, 70, 80, -60, 100, 110, -40, -20, 140))) + ), + existingFeature( + (x: (Coll[Int], (Coll[Int], Coll[Int]))) => x._1.updateMany(x._2._1, x._2._2), + "{ (x: (Coll[Int], (Coll[Int], Coll[Int]))) => x._1.updateMany(x._2._1, x._2._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SPair(SCollectionType(SInt), SCollectionType(SInt))))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse( + 1, + SPair(SCollectionType(SInt), SPair(SCollectionType(SInt), SCollectionType(SInt))) + ), + 2.toByte + ) + ) + ), + MethodCall.typed[Value[SCollection[SInt.type]]]( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SCollectionType(SInt), SCollectionType(SInt)))), + 1.toByte + ), + SCollection.getMethodByName("updateMany").withConcreteTypes(Map(STypeVar("IV") -> SInt)), + Vector( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(3, SPair(SCollectionType(SInt), SCollectionType(SInt))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(3, SPair(SCollectionType(SInt), SCollectionType(SInt))), + 2.toByte + ) + ), + Map() + ) + ) + )), + preGeneratedSamples = Some(samples)) + } + + // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + property("Coll find method equivalence") { + val find = newFeature((x: Coll[Int]) => x.find({ (v: Int) => v > 0 }), + "{ (x: Coll[Int]) => x.find({ (v: Int) => v > 0} ) }") + forAll { x: Coll[Int] => + find.checkEquality(x) + } + } + + // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 + property("Coll bitwise methods equivalence") { + val shiftRight = newFeature( + { (x: Coll[Boolean]) => + if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl[Boolean] + }, + "{ (x: Coll[Boolean]) => x >> 2 }") + forAll { x: Array[Boolean] => + shiftRight.checkEquality(Colls.fromArray(x)) + } + } + + // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + property("Coll diff methods equivalence") { + val diff = newFeature((x: (Coll[Int], Coll[Int])) => x._1.diff(x._2), + "{ (x: (Coll[Int], Coll[Int])) => x._1.diff(x._2) }") + forAll { (x: Coll[Int], y: Coll[Int]) => + diff.checkEquality((x, y)) + } + } + + property("Coll fold method equivalence") { + val n = ExactNumeric.IntIsExactNumeric + testCases( + // (coll, initState) + Seq( + ((Coll[Byte](), 0), Success(0)), + ((Coll[Byte](), Int.MaxValue), Success(Int.MaxValue)), + ((Coll[Byte](1), Int.MaxValue - 1), Success(Int.MaxValue)), + ((Coll[Byte](1), Int.MaxValue), Failure(new ArithmeticException("integer overflow"))), + ((Coll[Byte](-1), Int.MinValue + 1), Success(Int.MinValue)), + ((Coll[Byte](-1), Int.MinValue), Failure(new ArithmeticException("integer overflow"))), + ((Coll[Byte](1, 2), 0), Success(3)), + ((Coll[Byte](1, -1), 0), Success(0)), + ((Coll[Byte](1, -1, 1), 0), Success(1)) + ), + existingFeature( + { (x: (Coll[Byte], Int)) => x._1.foldLeft(x._2, { i: (Int, Byte) => n.plus(i._1, i._2) }) }, + "{ (x: (Coll[Byte], Int)) => x._1.fold(x._2, { (i1: Int, i2: Byte) => i1 + i2 }) }", + FuncValue( + Vector((1, SPair(SByteArray, SInt))), + Fold( + SelectField.typed[Value[SCollection[SByte.type]]](ValUse(1, SPair(SByteArray, SInt)), 1.toByte), + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SByteArray, SInt)), 2.toByte), + FuncValue( + Vector((3, SPair(SInt, SByte))), + ArithOp( + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SByte)), 1.toByte), + Upcast(SelectField.typed[Value[SByte.type]](ValUse(3, SPair(SInt, SByte)), 2.toByte), SInt), + OpCode @@ (-102.toByte) + ) + ) + ) + ))) + } + + property("Coll indexOf method equivalence") { + testCases( + // (coll, (elem: Byte, from: Int)) + Seq( + ((Coll[Byte](), (0.toByte, 0)), Success(-1)), + ((Coll[Byte](), (0.toByte, -1)), Success(-1)), + ((Coll[Byte](), (0.toByte, 1)), Success(-1)), + ((Coll[Byte](1), (0.toByte, 0)), Success(-1)), + ((Coll[Byte](1), (1.toByte, 0)), Success(0)), + ((Coll[Byte](1), (1.toByte, 1)), Success(-1)), + ((Coll[Byte](1, 1), (0.toByte, -1)), Success(-1)), + ((Coll[Byte](1, 1), (0.toByte, 0)), Success(-1)), + ((Coll[Byte](1, 1), (1.toByte, -1)), Success(0)), + ((Coll[Byte](1, 1), (1.toByte, 0)), Success(0)), + ((Coll[Byte](1, 1), (1.toByte, 1)), Success(1)), + ((Coll[Byte](1, 1), (1.toByte, 2)), Success(-1)), + ((Coll[Byte](1, 1), (1.toByte, 3)), Success(-1)), + ((Coll[Byte](1, 2, 3), (3.toByte, 0)), Success(2)), + ((Coll[Byte](1, 2, 3), (3.toByte, 1)), Success(2)), + ((Coll[Byte](1, 2, 3), (3.toByte, 2)), Success(2)), + ((Coll[Byte](1, 2, 3), (3.toByte, 3)), Success(-1)), + ((Helpers.decodeBytes("8085623fb7cd6b7f01801f00800100"), (0.toByte, -1)), Success(11)) + ), + existingFeature( + { (x: (Coll[Byte], (Byte, Int))) => x._1.indexOf(x._2._1, x._2._2) }, + "{ (x: (Coll[Byte], (Byte, Int))) => x._1.indexOf(x._2._1, x._2._2) }", + FuncValue( + Vector((1, SPair(SByteArray, SPair(SByte, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]](ValUse(1, SPair(SByteArray, SPair(SByte, SInt))), 2.toByte) + ) + ), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SPair(SByteArray, SPair(SByte, SInt))), + 1.toByte + ), + SCollection.getMethodByName("indexOf").withConcreteTypes(Map(STypeVar("IV") -> SByte)), + Vector( + SelectField.typed[Value[SByte.type]](ValUse(3, SPair(SByte, SInt)), 1.toByte), + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SByte, SInt)), 2.toByte) + ), + Map() + ) + ) + ))) + } + + property("Coll apply method equivalence") { + testCases( + Seq( + ((Coll[Int](), 0), Failure(new ArrayIndexOutOfBoundsException("0"))), + ((Coll[Int](), -1), Failure(new ArrayIndexOutOfBoundsException("-1"))), + ((Coll[Int](1), 0), Success(1)), + ((Coll[Int](1), 1), Failure(new ArrayIndexOutOfBoundsException("1"))), + ((Coll[Int](1), -1), Failure(new ArrayIndexOutOfBoundsException("-1"))), + ((Coll[Int](1, 2), 1), Success(2)), + ((Coll[Int](1, 2), 1), Success(2)), + ((Coll[Int](1, 2), 2), Failure(new ArrayIndexOutOfBoundsException("2"))) + ), + existingFeature((x: (Coll[Int], Int)) => x._1(x._2), + "{ (x: (Coll[Int], Int)) => x._1(x._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SInt))), + ByIndex( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SInt)), + 1.toByte + ), + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SCollectionType(SInt), SInt)), 2.toByte), + None + ) + ))) + } + + property("Coll getOrElse method equivalence") { + val default = 10 + testCases( + // (coll, (index, default)) + Seq( + ((Coll[Int](), (0, default)), Success(default)), + ((Coll[Int](), (-1, default)), Success(default)), + ((Coll[Int](1), (0, default)), Success(1)), + ((Coll[Int](1), (1, default)), Success(default)), + ((Coll[Int](1), (-1, default)), Success(default)), + ((Coll[Int](1, 2), (0, default)), Success(1)), + ((Coll[Int](1, 2), (1, default)), Success(2)), + ((Coll[Int](1, 2), (2, default)), Success(default)), + ((Coll[Int](1, 2), (-1, default)), Success(default)) + ), + existingFeature((x: (Coll[Int], (Int, Int))) => x._1.getOrElse(x._2._1, x._2._2), + "{ (x: (Coll[Int], (Int, Int))) => x._1.getOrElse(x._2._1, x._2._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SPair(SInt, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 2.toByte + ) + ) + ), + ByIndex( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 1.toByte + ), + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 1.toByte), + Some(SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 2.toByte)) + ) + ) + ))) + } + + property("Tuple size method equivalence") { + testCases( + Seq( + ((0, 0), Success(2)), + ((1, 2), Success(2)) + ), + existingFeature((x: (Int, Int)) => 2, + "{ (x: (Int, Int)) => x.size }", + FuncValue(Vector((1, SPair(SInt, SInt))), IntConstant(2)))) + } + + property("Tuple apply method equivalence") { + val samples = genSamples[(Int, Int)](DefaultMinSuccessful) + testCases( + Seq(((1, 2), Success(1))), + existingFeature((x: (Int, Int)) => x._1, + "{ (x: (Int, Int)) => x(0) }", + FuncValue( + Vector((1, SPair(SInt, SInt))), + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 1.toByte) + )), + preGeneratedSamples = Some(samples)) + testCases( + Seq(((1, 2), Success(2))), + existingFeature((x: (Int, Int)) => x._2, + "{ (x: (Int, Int)) => x(1) }", + FuncValue( + Vector((1, SPair(SInt, SInt))), + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 2.toByte) + )), + preGeneratedSamples = Some(samples)) + } + + property("Coll map method equivalence") { + val n = ExactNumeric.IntIsExactNumeric + testCases( + Seq( + (Coll[Int](), Success(Coll[Int]())), + (Coll[Int](1), Success(Coll[Int](2))), + (Coll[Int](1, 2), Success(Coll[Int](2, 3))), + (Coll[Int](1, 2, Int.MaxValue), Failure(new ArithmeticException("integer overflow"))) + ), + existingFeature((x: Coll[Int]) => x.map({ (v: Int) => n.plus(v, 1) }), + "{ (x: Coll[Int]) => x.map({ (v: Int) => v + 1 }) }", + FuncValue( + Vector((1, SCollectionType(SInt))), + MapCollection( + ValUse(1, SCollectionType(SInt)), + FuncValue(Vector((3, SInt)), ArithOp(ValUse(3, SInt), IntConstant(1), OpCode @@ (-102.toByte))) + ) + ))) + } + + property("Coll slice method equivalence") { + val samples = genSamples(collWithRangeGen, DefaultMinSuccessful) + testCases( + // (coll, (from, until)) + Seq( + ((Coll[Int](), (-1, 0)), Success(Coll[Int]())), + ((Coll[Int](), (0, 0)), Success(Coll[Int]())), + ((Coll[Int](1), (0, 0)), Success(Coll[Int]())), + ((Coll[Int](1), (0, -1)), Success(Coll[Int]())), + ((Coll[Int](1), (1, 1)), Success(Coll[Int]())), + ((Coll[Int](1), (-1, 1)), Success(Coll[Int](1))), + ((Coll[Int](1, 2), (1, 1)), Success(Coll[Int]())), + ((Coll[Int](1, 2), (1, 0)), Success(Coll[Int]())), + ((Coll[Int](1, 2), (1, 2)), Success(Coll[Int](2))), + ((Coll[Int](1, 2, 3, 4), (1, 3)), Success(Coll[Int](2, 3))) + ), + existingFeature((x: (Coll[Int], (Int, Int))) => x._1.slice(x._2._1, x._2._2), + "{ (x: (Coll[Int], (Int, Int))) => x._1.slice(x._2._1, x._2._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SPair(SInt, SInt)))), + BlockValue( + Vector( + ValDef( + 3, + List(), + SelectField.typed[Value[STuple]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 2.toByte + ) + ) + ), + Slice( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SPair(SInt, SInt))), + 1.toByte + ), + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 1.toByte), + SelectField.typed[Value[SInt.type]](ValUse(3, SPair(SInt, SInt)), 2.toByte) + ) + ) + )), + preGeneratedSamples = Some(samples)) + } + + property("Coll append method equivalence") { + testCases( + Seq( + (Coll[Int](), Coll[Int]()) -> Success(Coll[Int]()), + (Coll[Int](), Coll[Int](1)) -> Success(Coll[Int](1)), + (Coll[Int](1), Coll[Int]()) -> Success(Coll[Int](1)), + (Coll[Int](1), Coll[Int](2)) -> Success(Coll[Int](1, 2)), + (Coll[Int](1), Coll[Int](2, 3)) -> Success(Coll[Int](1, 2, 3)), + (Coll[Int](1, 2), Coll[Int](3)) -> Success(Coll[Int](1, 2, 3)), + (Coll[Int](1, 2), Coll[Int](3, 4)) -> Success(Coll[Int](1, 2, 3, 4)) + ), + existingFeature( + { (x: (Coll[Int], Coll[Int])) => x._1.append(x._2) }, + "{ (x: (Coll[Int], Coll[Int])) => x._1.append(x._2) }", + FuncValue( + Vector((1, SPair(SCollectionType(SInt), SCollectionType(SInt)))), + Append( + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SCollectionType(SInt))), + 1.toByte + ), + SelectField.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SPair(SCollectionType(SInt), SCollectionType(SInt))), + 2.toByte + ) + ) + ))) + } + + property("Option methods equivalence") { + testCases( + Seq( + (None -> Failure(new NoSuchElementException("None.get"))), + (Some(10L) -> Success(10L))), + existingFeature({ (x: Option[Long]) => x.get }, + "{ (x: Option[Long]) => x.get }", + FuncValue(Vector((1, SOption(SLong))), OptionGet(ValUse(1, SOption(SLong)))))) + + testCases( + Seq( + (None -> Success(false)), + (Some(10L) -> Success(true))), + existingFeature({ (x: Option[Long]) => x.isDefined }, + "{ (x: Option[Long]) => x.isDefined }", + FuncValue(Vector((1, SOption(SLong))), OptionIsDefined(ValUse(1, SOption(SLong)))))) + + testCases( + Seq( + (None -> Success(1L)), + (Some(10L) -> Success(10L))), + existingFeature({ (x: Option[Long]) => x.getOrElse(1L) }, + "{ (x: Option[Long]) => x.getOrElse(1L) }", + FuncValue(Vector((1, SOption(SLong))), OptionGetOrElse(ValUse(1, SOption(SLong)), LongConstant(1L))))) + + testCases( + Seq( + (None -> Success(None)), + (Some(10L) -> Success(None)), + (Some(1L) -> Success(Some(1L)))), + existingFeature({ (x: Option[Long]) => x.filter({ (v: Long) => v == 1} ) }, + "{ (x: Option[Long]) => x.filter({ (v: Long) => v == 1 }) }", + FuncValue( + Vector((1, SOption(SLong))), + MethodCall.typed[Value[SOption[SLong.type]]]( + ValUse(1, SOption(SLong)), + SOption.getMethodByName("filter").withConcreteTypes(Map(STypeVar("T") -> SLong)), + Vector(FuncValue(Vector((3, SLong)), EQ(ValUse(3, SLong), LongConstant(1L)))), + Map() + ) + ))) + + val n = ExactNumeric.LongIsExactNumeric + testCases( + Seq( + (None -> Success(None)), + (Some(10L) -> Success(Some(11L))), + (Some(Long.MaxValue) -> Failure(new ArithmeticException("long overflow")))), + existingFeature({ (x: Option[Long]) => x.map( (v: Long) => n.plus(v, 1) ) }, + "{ (x: Option[Long]) => x.map({ (v: Long) => v + 1 }) }", + FuncValue( + Vector((1, SOption(SLong))), + MethodCall.typed[Value[SOption[SLong.type]]]( + ValUse(1, SOption(SLong)), + SOption.getMethodByName("map").withConcreteTypes( + Map(STypeVar("T") -> SLong, STypeVar("R") -> SLong) + ), + Vector( + FuncValue( + Vector((3, SLong)), + ArithOp(ValUse(3, SLong), LongConstant(1L), OpCode @@ (-102.toByte)) + ) + ), + Map() + ) + ))) + } + + // TODO HF: implement Option.fold + property("Option new methods") { + val isEmpty = newFeature({ (x: Option[Long]) => x.isEmpty }, + "{ (x: Option[Long]) => x.isEmpty }") + + val n = ExactNumeric.LongIsExactNumeric + val fold = newFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, + "{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }") + + forAll { x: Option[Long] => + Seq(isEmpty, fold).map(_.checkEquality(x)) + } + } + + property("Option fold workaround method") { + val n = ExactNumeric.LongIsExactNumeric + testCases( + Seq( + (None -> Success(5L)), + (Some(0L) -> Success(1L)), + (Some(Long.MaxValue) -> Failure(new ArithmeticException("long overflow"))) + ), + existingFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, + """{(x: Option[Long]) => + | def f(opt: Long): Long = opt + 1 + | if (x.isDefined) f(x.get) else 5L + |}""".stripMargin, + FuncValue( + Vector((1, SOption(SLong))), + If( + OptionIsDefined(ValUse(1, SOption(SLong))), + Apply( + FuncValue( + Vector((3, SLong)), + ArithOp(ValUse(3, SLong), LongConstant(1L), OpCode @@ (-102.toByte)) + ), + Array(OptionGet(ValUse(1, SOption(SLong)))) + ), + LongConstant(5L) + ) + ))) + } + + property("blake2b256, sha256 equivalence") { + testCases( + Seq( + Coll[Byte]() -> Success(Helpers.decodeBytes("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8")), + Helpers.decodeBytes("e0ff0105ffffac31010017ff33") -> + Success(Helpers.decodeBytes("33707eed9aab64874ff2daa6d6a378f61e7da36398fb36c194c7562c9ff846b5")) + ), + existingFeature((x: Coll[Byte]) => SigmaDsl.blake2b256(x), + "{ (x: Coll[Byte]) => blake2b256(x) }", + FuncValue(Vector((1, SByteArray)), CalcBlake2b256(ValUse(1, SByteArray))))) + + testCases( + Seq( + Coll[Byte]() -> Success(Helpers.decodeBytes("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")), + Helpers.decodeBytes("e0ff0105ffffac31010017ff33") -> + Success(Helpers.decodeBytes("367d0ec2cdc14aac29d5beb60c2bfc86d5a44a246308659af61c1b85fa2ca2cc")) + ), + existingFeature((x: Coll[Byte]) => SigmaDsl.sha256(x), + "{ (x: Coll[Byte]) => sha256(x) }", + FuncValue(Vector((1, SByteArray)), CalcSha256(ValUse(1, SByteArray))))) + } + + property("print") { + println(ComplexityTableStat.complexityTableString) + } + + property("sigmaProp equivalence") { + testCases( + Seq( + (false, Success(CSigmaProp(TrivialProp.FalseProp))), + (true, Success(CSigmaProp(TrivialProp.TrueProp)))), + existingFeature((x: Boolean) => sigmaProp(x), + "{ (x: Boolean) => sigmaProp(x) }", + FuncValue(Vector((1, SBoolean)), BoolToSigmaProp(ValUse(1, SBoolean))))) + } + + property("atLeast equivalence") { + testCases( + Seq( + Coll[SigmaProp]( + CSigmaProp( + ProveDHTuple( + Helpers.decodeECPoint("028cf1686a3275e54441c68d789151bdec40d34d62188bbadce170d96d4ce399b0"), + Helpers.decodeECPoint("03e53afbe18efff6586f5b8bda1c3262aac65ede384f12b4a56ecb74e16d73efc5"), + Helpers.decodeECPoint("02614b14a8c6c6b4b7ce017d72fbca7f9218b72c16bdd88f170ffb300b106b9014"), + Helpers.decodeECPoint("034cc5572276adfa3e283a3f1b0f0028afaadeaa362618c5ec43262d8cefe7f004") + ) + )) -> Success(CSigmaProp(TrivialProp.TrueProp)), + Coll[SigmaProp]( + CSigmaProp( + ProveDHTuple( + Helpers.decodeECPoint("028cf1686a3275e54441c68d789151bdec40d34d62188bbadce170d96d4ce399b0"), + Helpers.decodeECPoint("03e53afbe18efff6586f5b8bda1c3262aac65ede384f12b4a56ecb74e16d73efc5"), + Helpers.decodeECPoint("02614b14a8c6c6b4b7ce017d72fbca7f9218b72c16bdd88f170ffb300b106b9014"), + Helpers.decodeECPoint("034cc5572276adfa3e283a3f1b0f0028afaadeaa362618c5ec43262d8cefe7f004") + ) + ), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03f7eacae7476a9ef082513a6a70ed6b208aafad0ade5f614ac6cfa2176edd0d69"))), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("023bddd50b917388cd2c4f478f3ea9281bf03a252ee1fefe9c79f800afaa8d86ad"))) + ) -> Success( + CSigmaProp( + CTHRESHOLD( + 2, + Array( + ProveDHTuple( + Helpers.decodeECPoint("028cf1686a3275e54441c68d789151bdec40d34d62188bbadce170d96d4ce399b0"), + Helpers.decodeECPoint("03e53afbe18efff6586f5b8bda1c3262aac65ede384f12b4a56ecb74e16d73efc5"), + Helpers.decodeECPoint("02614b14a8c6c6b4b7ce017d72fbca7f9218b72c16bdd88f170ffb300b106b9014"), + Helpers.decodeECPoint("034cc5572276adfa3e283a3f1b0f0028afaadeaa362618c5ec43262d8cefe7f004") + ), + ProveDlog(Helpers.decodeECPoint("03f7eacae7476a9ef082513a6a70ed6b208aafad0ade5f614ac6cfa2176edd0d69")), + ProveDlog(Helpers.decodeECPoint("023bddd50b917388cd2c4f478f3ea9281bf03a252ee1fefe9c79f800afaa8d86ad")) + ) + ) + ) + ) + ), + existingFeature((x: Coll[SigmaProp]) => SigmaDsl.atLeast(x.size - 1, x), + "{ (x: Coll[SigmaProp]) => atLeast(x.size - 1, x) }", + FuncValue( + Vector((1, SCollectionType(SSigmaProp))), + AtLeast( + ArithOp(SizeOf(ValUse(1, SCollectionType(SSigmaProp))), IntConstant(1), OpCode @@ (-103.toByte)), + ValUse(1, SCollectionType(SSigmaProp)) + ) + ))) + } + + property("&& sigma equivalence") { + testCases( + Seq( + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798"))), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success( + CSigmaProp( + CAND( + Seq( + ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798")), + ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")) + ) + ) + ) + ), + (CSigmaProp(TrivialProp.TrueProp), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(TrivialProp.FalseProp), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success(CSigmaProp(TrivialProp.FalseProp)), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), + CSigmaProp(TrivialProp.TrueProp)) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), + CSigmaProp(TrivialProp.FalseProp)) -> + Success(CSigmaProp(TrivialProp.FalseProp)) + ), + existingFeature( + (x: (SigmaProp, SigmaProp)) => x._1 && x._2, + "{ (x:(SigmaProp, SigmaProp)) => x._1 && x._2 }", + FuncValue( + Vector((1, SPair(SSigmaProp, SSigmaProp))), + SigmaAnd( + Seq( + SelectField.typed[Value[SSigmaProp.type]](ValUse(1, SPair(SSigmaProp, SSigmaProp)), 1.toByte), + SelectField.typed[Value[SSigmaProp.type]](ValUse(1, SPair(SSigmaProp, SSigmaProp)), 2.toByte) + ) + ) + ))) + + testCases( + Seq( + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), true) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), false) -> + Success(CSigmaProp(TrivialProp.FalseProp)) + ), + existingFeature( + (x: (SigmaProp, Boolean)) => x._1 && sigmaProp(x._2), + "{ (x:(SigmaProp, Boolean)) => x._1 && sigmaProp(x._2) }", + FuncValue( + Vector((1, SPair(SSigmaProp, SBoolean))), + SigmaAnd( + Seq( + SelectField.typed[Value[SSigmaProp.type]](ValUse(1, SPair(SSigmaProp, SBoolean)), 1.toByte), + BoolToSigmaProp( + SelectField.typed[Value[SBoolean.type]](ValUse(1, SPair(SSigmaProp, SBoolean)), 2.toByte) + ) + ) + ) + ))) + } + + property("|| sigma equivalence") { + testCases( + Seq( + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798"))), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success( + CSigmaProp( + COR( + Seq( + ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798")), + ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")) + ) + ) + ) + ), + (CSigmaProp(TrivialProp.FalseProp), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(TrivialProp.TrueProp), + CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> + Success(CSigmaProp(TrivialProp.TrueProp)), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), + CSigmaProp(TrivialProp.FalseProp)) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), + CSigmaProp(TrivialProp.TrueProp)) -> + Success(CSigmaProp(TrivialProp.TrueProp)) + ), + existingFeature( + (x: (SigmaProp, SigmaProp)) => x._1 || x._2, + "{ (x:(SigmaProp, SigmaProp)) => x._1 || x._2 }", + FuncValue( + Vector((1, SPair(SSigmaProp, SSigmaProp))), + SigmaOr( + Seq( + SelectField.typed[SigmaPropValue](ValUse(1, SPair(SSigmaProp, SSigmaProp)), 1.toByte), + SelectField.typed[SigmaPropValue](ValUse(1, SPair(SSigmaProp, SSigmaProp)), 2.toByte) + ) + ) + ))) + + testCases( + Seq( + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), false) -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), + (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), true) -> + Success(CSigmaProp(TrivialProp.TrueProp)) + ), + existingFeature( + (x: (SigmaProp, Boolean)) => x._1 || sigmaProp(x._2), + "{ (x:(SigmaProp, Boolean)) => x._1 || sigmaProp(x._2) }", + FuncValue( + Vector((1, SPair(SSigmaProp, SBoolean))), + SigmaOr( + Seq( + SelectField.typed[SigmaPropValue](ValUse(1, SPair(SSigmaProp, SBoolean)), 1.toByte), + BoolToSigmaProp( + SelectField.typed[BoolValue](ValUse(1, SPair(SSigmaProp, SBoolean)), 2.toByte) + ) + ) + ) + ))) + } + + property("SigmaProp.propBytes equivalence") { + testCases( + Seq( + CSigmaProp(ProveDlog(Helpers.decodeECPoint("039d0b1e46c21540d033143440d2fb7dd5d650cf89981c99ee53c6e0374d2b1b6f"))) -> + Success(Helpers.decodeBytes("0008cd039d0b1e46c21540d033143440d2fb7dd5d650cf89981c99ee53c6e0374d2b1b6f")) + ), + existingFeature((x: SigmaProp) => x.propBytes, + "{ (x: SigmaProp) => x.propBytes }", + FuncValue(Vector((1, SSigmaProp)), SigmaPropBytes(ValUse(1, SSigmaProp))))) + } + + // TODO HF: implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + property("allZK equivalence") { + lazy val allZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.allZK(x), + "{ (x: Coll[SigmaProp]) => allZK(x) }") + forAll { x: Coll[SigmaProp] => + allZK.checkEquality(x) + } + } + + // TODO HF: implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + property("anyZK equivalence") { + lazy val anyZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.anyZK(x), + "{ (x: Coll[SigmaProp]) => anyZK(x) }") + forAll { x: Coll[SigmaProp] => + anyZK.checkEquality(x) + } + } + + property("allOf equivalence") { + testCases( + Seq( + (Coll[Boolean]() -> Success(true)), + (Coll[Boolean](true) -> Success(true)), + (Coll[Boolean](false) -> Success(false)), + (Coll[Boolean](false, false) -> Success(false)), + (Coll[Boolean](false, true) -> Success(false)), + (Coll[Boolean](true, false) -> Success(false)), + (Coll[Boolean](true, true) -> Success(true)), + (Coll[Boolean](true, false, false) -> Success(false)), + (Coll[Boolean](true, false, true) -> Success(false)), + (Coll[Boolean](true, true, false) -> Success(false)), + (Coll[Boolean](true, true, true) -> Success(true)) + ), + existingFeature((x: Coll[Boolean]) => SigmaDsl.allOf(x), + "{ (x: Coll[Boolean]) => allOf(x) }", + FuncValue(Vector((1, SBooleanArray)), AND(ValUse(1, SBooleanArray))))) + } + + property("anyOf equivalence") { + testCases( + Seq( + (Coll[Boolean]() -> Success(false)), + (Coll[Boolean](true) -> Success(true)), + (Coll[Boolean](false) -> Success(false)), + (Coll[Boolean](false, false) -> Success(false)), + (Coll[Boolean](false, true) -> Success(true)), + (Coll[Boolean](true, false) -> Success(true)), + (Coll[Boolean](true, true) -> Success(true)), + (Coll[Boolean](true, false, false) -> Success(true)), + (Coll[Boolean](true, false, true) -> Success(true)), + (Coll[Boolean](true, true, false) -> Success(true)), + (Coll[Boolean](true, true, true) -> Success(true)) + ), + existingFeature((x: Coll[Boolean]) => SigmaDsl.anyOf(x), + "{ (x: Coll[Boolean]) => anyOf(x) }", + FuncValue(Vector((1, SBooleanArray)), OR(ValUse(1, SBooleanArray))))) + } + + property("proveDlog equivalence") { + testCases( + Seq( + (Helpers.decodeGroupElement("02288f0e55610c3355c89ed6c5de43cf20da145b8c54f03a29f481e540d94e9a69") -> + Success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("02288f0e55610c3355c89ed6c5de43cf20da145b8c54f03a29f481e540d94e9a69"))))) + ), + existingFeature({ (x: GroupElement) => SigmaDsl.proveDlog(x) }, + "{ (x: GroupElement) => proveDlog(x) }", + FuncValue(Vector((1, SGroupElement)), CreateProveDlog(ValUse(1, SGroupElement))))) + } + + property("proveDHTuple equivalence") { + testCases( + Seq( + (Helpers.decodeGroupElement("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a") -> Success( + CSigmaProp( + ProveDHTuple( + Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a"), + Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a"), + Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a"), + Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a") + ) + ) + )) + ), + existingFeature({ (x: GroupElement) => SigmaDsl.proveDHTuple(x, x, x, x) }, + "{ (x: GroupElement) => proveDHTuple(x, x, x, x) }", + FuncValue( + Vector((1, SGroupElement)), + CreateProveDHTuple( + ValUse(1, SGroupElement), + ValUse(1, SGroupElement), + ValUse(1, SGroupElement), + ValUse(1, SGroupElement) + ) + ))) + } + +} diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTest.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTest.scala deleted file mode 100644 index 07e5c0e582..0000000000 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTest.scala +++ /dev/null @@ -1,1029 +0,0 @@ -package special.sigma - -import java.math.BigInteger - -import org.ergoplatform.ErgoScriptPredef.TrueProp -import org.ergoplatform.dsl.{SigmaContractSyntax, TestContractSpec} -import org.ergoplatform._ -import org.scalatest.prop.PropertyChecks -import org.scalatest.{PropSpec, Matchers} -import scalan.RType -import scorex.crypto.authds.avltree.batch._ -import scorex.crypto.authds.{ADKey, ADValue} -import scorex.crypto.hash.{Digest32, Blake2b256} -import scalan.util.Extensions._ -import sigma.util.Extensions._ -import sigmastate.Values.IntConstant -import sigmastate._ -import sigmastate.Values._ -import sigmastate.eval.Extensions._ -import sigmastate.eval._ -import sigmastate.interpreter.Interpreter.ScriptEnv -import sigmastate.utxo.ComplexityTableStat -import special.collection.{Coll, Builder} - -import scala.util.{Success, Try, Failure} - - -/** This suite tests every method of every SigmaDsl type to be equivalent to - * the evaluation of the corresponding ErgoScript operation */ -class SigmaDslTest extends PropSpec - with PropertyChecks - with Matchers - with SigmaTestingData with SigmaContractSyntax - with SigmaTypeGens { suite => - - lazy val spec = TestContractSpec(suite)(new TestingIRContext) - - override def contractEnv: ScriptEnv = Map() - - implicit lazy val IR = new TestingIRContext { - override val okPrintEvaluatedEntries: Boolean = false - override val okMeasureOperationTime: Boolean = true - } - - def checkEq[A,B](f: A => B)(g: A => B): A => Unit = { x: A => - val b1 = Try(f(x)); val b2 = Try(g(x)) - (b1, b2) match { - case (Success(b1), Success(b2)) => - assert(b1 == b2) - case (Failure(t1), Failure(t2)) => - val c1 = rootCause(t1).getClass - val c2 = rootCause(t2).getClass - c1 shouldBe c2 - case _ => - assert(false) - } - - } - - def checkEq2[A,B,R](f: (A, B) => R)(g: (A, B) => R): (A,B) => Unit = { (x: A, y: B) => - val r1 = f(x, y); val r2 = g(x, y) - assert(r1.getClass == r2.getClass) - assert(r1 == r2) - } - - def getRandomIndex(size: Int): Int = { - val r = scala.util.Random - if (size > 1) r.nextInt(size) else 0 - } - - def makeSlicePair(size: Int): (Int, Int) = { - val r = scala.util.Random - val rBorder = getRandomIndex(size) - val lBorder = getRandomIndex(rBorder) - (lBorder, rBorder) - } - - // TODO: make more effective - def generateIndexColl(maxSize: Int): Coll[Int] = { - var ret: Coll[Int] = Colls.emptyColl - var index = getRandomIndex(maxSize) - while (index > 0) { - ret = ret.append(Colls.fromArray(Array(index))) - index = getRandomIndex(index) - } - ret - } - - case class EqualityChecker[T: RType](obj: T) { - def apply[R: RType](dslFunc: T => R)(script: String) = - checkEq(func[T, R](script))(dslFunc)(obj) - } - - ignore("Boolean methods equivalence") { - lazy val toByte = checkEq(func[Boolean,Byte]("{ (x: Boolean) => x.toByte }"))((x: Boolean) => x.toByte) - forAll { x: Boolean => - //TODO soft-fork: for new operation below - Seq(toByte).foreach(_(x)) - } - } - - property("Byte methods equivalence") { - val toByte = checkEq(func[Byte, Byte]("{ (x: Byte) => x.toByte }"))(x => x.toByte) - val toShort = checkEq(func[Byte,Short]("{ (x: Byte) => x.toShort }"))(x => x.toShort) - val toInt = checkEq(func[Byte,Int]("{ (x: Byte) => x.toInt }"))(x => x.toInt) - val toLong = checkEq(func[Byte,Long]("{ (x: Byte) => x.toLong }"))(x => x.toLong) - val toBigInt = checkEq(func[Byte,BigInt]("{ (x: Byte) => x.toBigInt }"))(x => x.toBigInt) - - //TODO soft-fork: for new 4 operations below - lazy val toBytes = checkEq(func[Byte,Coll[Byte]]("{ (x: Byte) => x.toBytes }"))(x => x.toBytes) - lazy val toBits = checkEq(func[Byte,Coll[Boolean]]("{ (x: Byte) => x.toBits }"))(x => x.toBits) - lazy val toAbs = checkEq(func[Byte,Byte]("{ (x: Byte) => x.toAbs }"))(x => x.toAbs) - lazy val compareTo = checkEq(func[(Byte, Byte), Int]("{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }"))({ (x: (Byte, Byte)) => x._1.compareTo(x._2) }) - - forAll { x: Byte => - Seq(toByte, toShort, toInt, toLong, toBigInt).foreach(_(x)) - } - println(ComplexityTableStat.complexityTableString) - } - - property("Int methods equivalence") { - val toByte = checkEq(func[Int,Byte]("{ (x: Int) => x.toByte }"))(x => x.toByte) - val toShort = checkEq(func[Int,Short]("{ (x: Int) => x.toShort }"))(x => x.toShort) - val toInt = checkEq(func[Int,Int]("{ (x: Int) => x.toInt }"))(x => x.toInt) - val toLong = checkEq(func[Int,Long]("{ (x: Int) => x.toLong }"))(x => x.toLong) - val toBigInt = checkEq(func[Int,BigInt]("{ (x: Int) => x.toBigInt }"))(x => x.toBigInt) - lazy val toBytes = checkEq(func[Int,Coll[Byte]]("{ (x: Int) => x.toBytes }"))(x => x.toBytes) - lazy val toBits = checkEq(func[Int,Coll[Boolean]]("{ (x: Int) => x.toBits }"))(x => x.toBits) - lazy val toAbs = checkEq(func[Int,Int]("{ (x: Int) => x.toAbs }"))(x => x.toAbs) - lazy val compareTo = checkEq(func[(Int, Int), Int]("{ (x: (Int, Int)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) - - forAll { x: Byte => toByte(x.toInt) } - forAll { x: Short => toShort(x.toInt) } - forAll { x: Int => - Seq(toInt, toLong, toBigInt).foreach(_(x)) - //TODO soft-fork: toBytes, toBits, toAbs - } - forAll { x: (Int, Int) => - //TODO soft-fork: compareTo(x) - } - } - - property("Short methods equivalence") { - val toByte = checkEq(func[Short,Byte]("{ (x: Short) => x.toByte }"))(x => x.toByte) - val toShort = checkEq(func[Short,Short]("{ (x: Short) => x.toShort }"))(x => x.toShort) - val toInt = checkEq(func[Short,Int]("{ (x: Short) => x.toInt }"))(x => x.toInt) - val toLong = checkEq(func[Short,Long]("{ (x: Short) => x.toLong }"))(x => x.toLong) - val toBigInt = checkEq(func[Short,BigInt]("{ (x: Short) => x.toBigInt }"))(x => x.toBigInt) - lazy val toBytes = checkEq(func[Short,Coll[Byte]]("{ (x: Short) => x.toBytes }"))(x => x.toBytes) - lazy val toBits = checkEq(func[Short,Coll[Boolean]]("{ (x: Short) => x.toBits }"))(x => x.toBits) - // TODO: Implement Short.toAbs - lazy val toAbs = checkEq(func[Short,Short]("{ (x: Short) => x.toAbs }"))((x: Short) => if (x >= 0.toShort) x else (-x).toShort) - lazy val compareTo = checkEq(func[(Short, Short), Int]("{ (x: (Short, Short)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) - - forAll { x: Byte => toByte(x.toShort) } - forAll { x: Short => - Seq(toShort, toInt, toLong, toBigInt).foreach(_(x)) - //TODO soft-fork: toBytes, toBits, toAbs - } - forAll { x: (Short, Short) => - //TODO soft-fork: compareTo(x) - } - } - - property("Long methods equivalence") { - val toByte = checkEq(func[Long,Byte]("{ (x: Long) => x.toByte }"))(x => x.toByte) - val toShort = checkEq(func[Long,Short]("{ (x: Long) => x.toShort }"))(x => x.toShort) - val toInt = checkEq(func[Long,Int]("{ (x: Long) => x.toInt }"))(x => x.toInt) - val toLong = checkEq(func[Long,Long]("{ (x: Long) => x.toLong }"))(x => x.toLong) - val toBigInt = checkEq(func[Long,BigInt]("{ (x: Long) => x.toBigInt }"))(x => BigInt(x).bigInteger) - /* - lazy val toBytes = checkEq(func[Long,Coll[Byte]]("{ (x: Long) => x.toBytes }"))(x => x.toBytes) - lazy val toBits = checkEq(func[Long,Coll[Boolean]]("{ (x: Long) => x.toBits }"))(x => x.toBits) - lazy val toAbs = checkEq(func[Long,Long]("{ (x: Long) => x.toAbs }"))(x => x.toAbs) - */ - lazy val compareTo = checkEq(func[(Long, Long), Int]("{ (x: (Long, Long)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) - - forAll { x: Byte => toByte(x.toLong) } - forAll { x: Short => toShort(x.toLong) } - forAll { x: Int => toInt(x.toLong) } - - forAll { x: Long => - Seq(toLong, toBigInt).foreach(_(x)) - //TODO soft-fork: toBytes, toBits, toAbs - } - forAll { x: (Long, Long) => - //TODO soft-fork: compareTo(x) - } - } - property("BigInt methods equivalence") { - val toByte = checkEq(func[(Byte, BigInt),Boolean]("{ (x: (Byte, BigInt)) => x._2.toByte == x._1 }")) { x => - x._2.toByte == x._1 - } - val toShort = checkEq(func[(Short, BigInt),Boolean]("{ (x: (Short, BigInt)) => x._2.toShort == x._1 }")) { x => - x._2.toShort == x._1 - } - val toInt = checkEq(func[(Int, BigInt),Boolean]("{ (x: (Int, BigInt)) => x._2.toInt == x._1 }")) { x => - x._2.toInt == x._1 - } - val toLong = checkEq(func[(Long, BigInt),Boolean]("{ (x: (Long, BigInt)) => x._2.toLong == x._1 }")) { x => - x._2.toLong == x._1 - } - val toBigInt = checkEq(func[(BigInt, BigInt),Boolean]("{ (x: (BigInt, BigInt)) => x._2.toBigInt == x._1 }")) { x => - x._2 == x._1 - } - - lazy val toBytes = checkEq(func[BigInt,Coll[Byte]]("{ (x: BigInt) => x.toBytes }"))(x => x.toBytes) - lazy val toBits = checkEq(func[BigInt,Coll[Boolean]]("{ (x: BigInt) => x.toBits }"))(x => x.toBits) - lazy val toAbs = checkEq(func[BigInt,BigInt]("{ (x: BigInt) => x.toAbs }"))(x => x.toAbs) - lazy val compareTo = checkEq(func[(BigInt, BigInt), Int]("{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) - - /* - forAll { x: Byte => - toByte((x, x.toBigInt)) - } - forAll { x: Short => - toShort((x, x.toBigInt)) - } - forAll { x: Int => - toInt((x, x.toBigInt)) - } - forAll { x: Long => - toLong((x, BigInt(x).bigInteger)) - } - */ - forAll { x: BigInt => - Seq(toBigInt).foreach(_((x, x))) - //TODO soft-fork: toBytes, toBits, toAbs - } - forAll { x: (BigInt, BigInt) => - //TODO soft-fork: compareTo(x) - } - } - - property("BigInt plus equivalence") { - forAll { v: (BigInt, BigInt) => - checkEq(func[(BigInt, BigInt),BigInt]( "{ (x: (BigInt, BigInt)) => x._1 + x._2 }")){ x => x._1 + x._2 }(v) - } - } - - property("BigInt minus equivalence") { - forAll { v: (BigInt, BigInt) => - checkEq(func[(BigInt, BigInt),BigInt]( "{ (x: (BigInt, BigInt)) => x._1 - x._2 }")){ x => x._1 - x._2 }(v) - } - } - - property("BigInt multiply equivalence") { - forAll { v: (BigInt, BigInt) => - checkEq(func[(BigInt, BigInt),BigInt]( "{ (x: (BigInt, BigInt)) => x._1 * x._2 }")){ x => x._1 * x._2 }(v) - } - } - - property("BigInt divide equivalence") { - forAll { v: (BigInt, BigInt) => - whenever(v._2.compareTo(0.toBigInt) > 0) { - checkEq(func[(BigInt, BigInt), BigInt]("{ (x: (BigInt, BigInt)) => x._1 / x._2 }")) { x => x._1 / x._2 }(v) - } - } - } - - property("BigInt mod equivalence") { - forAll { v: (BigInt, BigInt) => - whenever(v._2.compareTo(0.toBigInt) > 0) { - checkEq(func[(BigInt, BigInt), BigInt]("{ (x: (BigInt, BigInt)) => x._1 % x._2 }")) { x => - x._1 % x._2 - }(v) - } - } - } - - property("GroupElement operations equivalence") { - val ge = SigmaDsl.groupGenerator - val n = SigmaDsl.BigInt(BigInteger.TEN) - val g2 = ge.exp(n) - - { - val eq = EqualityChecker(ge) - eq({ (x: GroupElement) => x.getEncoded })("{ (x: GroupElement) => x.getEncoded }") - eq({ (x: GroupElement) => decodePoint(x.getEncoded) == x })("{ (x: GroupElement) => decodePoint(x.getEncoded) == x }") - eq({ (x: GroupElement) => x.negate })("{ (x: GroupElement) => x.negate }") - - //TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - // eq({ (x: GroupElement) => x.isIdentity })("{ (x: GroupElement) => x.isIdentity }") - } - - { - val eq = EqualityChecker((ge, n)) - eq({ (x: (GroupElement, BigInt)) => x._1.exp(x._2) })("{ (x: (GroupElement, BigInt)) => x._1.exp(x._2) }") - } - - { - val eq = EqualityChecker((ge, g2)) - eq({ (x: (GroupElement, GroupElement)) => x._1.multiply(x._2) })("{ (x: (GroupElement, GroupElement)) => x._1.multiply(x._2) }") - } - - { - val eq = checkEq(func[BigInt, GroupElement]("{ (x: BigInt) => groupGenerator.exp(x) }")) { x => - groupGenerator.exp(x) - } - forAll { x: BigInt => - eq(x) - } - } - } - - property("AvlTree properties equivalence") { - val doDigest = checkEq(func[AvlTree, Coll[Byte]]("{ (t: AvlTree) => t.digest }")) { (t: AvlTree) => t.digest } - val doEnabledOps = checkEq(func[AvlTree, Byte]( - "{ (t: AvlTree) => t.enabledOperations }")) { (t: AvlTree) => t.enabledOperations } - val doKeyLength = checkEq(func[AvlTree, Int]("{ (t: AvlTree) => t.keyLength }")) { (t: AvlTree) => t.keyLength } - val doValueLength = checkEq(func[AvlTree, Option[Int]]("{ (t: AvlTree) => t.valueLengthOpt }")) { (t: AvlTree) => t.valueLengthOpt } - val insertAllowed = checkEq(func[AvlTree, Boolean]("{ (t: AvlTree) => t.isInsertAllowed }")) { (t: AvlTree) => t.isInsertAllowed } - val updateAllowed = checkEq(func[AvlTree, Boolean]("{ (t: AvlTree) => t.isUpdateAllowed }")) { (t: AvlTree) => t.isUpdateAllowed } - val removeAllowed = checkEq(func[AvlTree, Boolean]("{ (t: AvlTree) => t.isRemoveAllowed }")) { (t: AvlTree) => t.isRemoveAllowed } - - val newTree = sampleAvlTree.updateOperations(1.toByte) - val trees = Array(sampleAvlTree, newTree) - - for (tree <- trees) { - doDigest(tree) - doEnabledOps(tree) - doKeyLength(tree) - doValueLength(tree) - insertAllowed(tree) - updateAllowed(tree) - removeAllowed(tree) - } - } - - property("AvlTree.{contains, get, getMany, updateDigest, updateOperations} equivalence") { - val doContains = checkEq( - func[(AvlTree, (Coll[Byte], Coll[Byte])), Boolean]( - "{ (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.contains(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.contains(t._2._1, t._2._2) } - val doGet = checkEq( - func[(AvlTree, (Coll[Byte], Coll[Byte])), Option[Coll[Byte]]]( - "{ (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.get(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[Byte], Coll[Byte]))) => t._1.get(t._2._1, t._2._2) } - val doGetMany = checkEq( - func[(AvlTree, (Coll[Coll[Byte]], Coll[Byte])), Coll[Option[Coll[Byte]]]]( - "{ (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.getMany(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.getMany(t._2._1, t._2._2) } - val doUpdateDigest = checkEq( - func[(AvlTree, Coll[Byte]), AvlTree]( - "{ (t: (AvlTree, Coll[Byte])) => t._1.updateDigest(t._2) }")) - { (t: (AvlTree, Coll[Byte])) => t._1.updateDigest(t._2) } - val doUpdateOperations = checkEq( - func[(AvlTree, Byte), AvlTree]( - "{ (t: (AvlTree, Byte)) => t._1.updateOperations(t._2) }")) - { (t: (AvlTree, Byte)) => t._1.updateOperations(t._2) } - - val (key, _, avlProver) = sampleAvlProver - avlProver.performOneOperation(Lookup(ADKey @@ key.toArray)) - val digest = avlProver.digest.toColl - val proof = avlProver.generateProof().toColl - val tree = SigmaDsl.avlTree(AvlTreeFlags.ReadOnly.serializeToByte, digest, 32, None) - doContains((tree, (key, proof))) - doGet((tree, (key, proof))) - val keys = Colls.fromItems(key) - doGetMany((tree, (keys, proof))) - doUpdateDigest(tree, digest) - doUpdateOperations(tree, 1.toByte) - } - - property("AvlTree.{insert, update, remove} equivalence") { - type KV = (Coll[Byte], Coll[Byte]) - val doInsert = checkEq( - func[(AvlTree, (Coll[KV], Coll[Byte])), Option[AvlTree]]( - "{ (t: (AvlTree, (Coll[(Coll[Byte], Coll[Byte])], Coll[Byte]))) => t._1.insert(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[KV], Coll[Byte]))) => t._1.insert(t._2._1, t._2._2) } - val doUpdate = checkEq( - func[(AvlTree, (Coll[KV], Coll[Byte])), Option[AvlTree]]( - "{ (t: (AvlTree, (Coll[(Coll[Byte], Coll[Byte])], Coll[Byte]))) => t._1.update(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[KV], Coll[Byte]))) => t._1.update(t._2._1, t._2._2) } - val doRemove = checkEq( - func[(AvlTree, (Coll[Coll[Byte]], Coll[Byte])), Option[AvlTree]]( - "{ (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.remove(t._2._1, t._2._2) }")) - { (t: (AvlTree, (Coll[Coll[Byte]], Coll[Byte]))) => t._1.remove(t._2._1, t._2._2) } - - val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) - val key = Array.fill(32)(1.toByte).toColl - - { - val preInsertDigest = avlProver.digest.toColl - val value = bytesCollGen.sample.get - avlProver.performOneOperation(Insert(ADKey @@ key.toArray, ADValue @@ value.toArray)) - val insertProof = avlProver.generateProof().toColl - val preInsertTree = SigmaDsl.avlTree(AvlTreeFlags(true, false, false).serializeToByte, preInsertDigest, 32, None) - val insertKvs = Colls.fromItems((key -> value)) - doInsert((preInsertTree, (insertKvs, insertProof))) - } - - { - val preUpdateDigest = avlProver.digest.toColl - val newValue = bytesCollGen.sample.get - avlProver.performOneOperation(Update(ADKey @@ key.toArray, ADValue @@ newValue.toArray)) - val updateProof = avlProver.generateProof().toColl - val preUpdateTree = SigmaDsl.avlTree(AvlTreeFlags(false, true, false).serializeToByte, preUpdateDigest, 32, None) - val updateKvs = Colls.fromItems((key -> newValue)) - doUpdate((preUpdateTree, (updateKvs, updateProof))) - } - - { - val preRemoveDigest = avlProver.digest.toColl - avlProver.performOneOperation(Remove(ADKey @@ key.toArray)) - val removeProof = avlProver.generateProof().toColl - val preRemoveTree = SigmaDsl.avlTree(AvlTreeFlags(false, false, true).serializeToByte, preRemoveDigest, 32, None) - val removeKeys = Colls.fromItems(key) - doRemove((preRemoveTree, (removeKeys, removeProof))) - } - } - - property("longToByteArray equivalence") { - val eq = checkEq(func[Long, Coll[Byte]]("{ (x: Long) => longToByteArray(x) }")){ x => - longToByteArray(x) - } - forAll { x: Long => eq(x) } - } - - property("byteArrayToBigInt equivalence") { - val eq = checkEq(func[Coll[Byte], BigInt]("{ (x: Coll[Byte]) => byteArrayToBigInt(x) }")){ x => - byteArrayToBigInt(x) - } - forAll { x: Array[Byte] => - whenever(x.length <= SigmaConstants.MaxBigIntSizeInBytes.value) { - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - } - - property("byteArrayToLong equivalence") { - val eq = checkEq(func[Coll[Byte],Long]("{ (x: Coll[Byte]) => byteArrayToLong(x) }")){ x => - byteArrayToLong(x) - } - forAll { x: Array[Byte] => - whenever(x.length >= 8) { - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/427 - // TODO costing: expression t._1(t._2) cannot be costed because t is lambda argument - // ignore("Func context variable") { - // val doApply = checkEq(func[(Int => Int, Int), Int]("{ (t: (Int => Int, Int)) => t._1(t._2) }")) { (t: (Int => Int, Int)) => t._1(t._2) } - // val code = compileWithCosting(emptyEnv, s"{ (x: Int) => x + 1 }") - // val ctx = ErgoLikeContext.dummy(fakeSelf) - // doApply((CFunc[Int, Int](ctx, code), 10)) - // } - - lazy val ctx = ergoCtx.toSigmaContext(IR, false) - - property("Box properties equivalence") { - val box = ctx.dataInputs(0) - val eq = EqualityChecker(box) - eq({ (x: Box) => x.id })("{ (x: Box) => x.id }") - eq({ (x: Box) => x.value })("{ (x: Box) => x.value }") - eq({ (x: Box) => x.propositionBytes })("{ (x: Box) => x.propositionBytes }") - eq({ (x: Box) => x.bytes })("{ (x: Box) => x.bytes }") - eq({ (x: Box) => x.bytesWithoutRef })("{ (x: Box) => x.bytesWithoutRef }") - eq({ (x: Box) => x.creationInfo })("{ (x: Box) => x.creationInfo }") - eq({ (x: Box) => x.tokens })("{ (x: Box) => x.tokens }") - } - - property("Advanced Box test") { - val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) - avlProver.generateProof() - - val digest = avlProver.digest - val treeData = SigmaDsl.avlTree(new AvlTreeData(digest, AvlTreeFlags.ReadOnly, 32, None)) - val box = ctx.dataInputs(0) - - val s = ErgoBox(20, TrueProp, 0, Seq(),Map( - ErgoBox.nonMandatoryRegisters(0) -> ByteConstant(1.toByte), - ErgoBox.nonMandatoryRegisters(1) -> ShortConstant(1024.toShort), - ErgoBox.nonMandatoryRegisters(2) -> IntConstant(1024 * 1024), - ErgoBox.nonMandatoryRegisters(3) -> LongConstant(1024.toLong), - ErgoBox.nonMandatoryRegisters(4) -> BigIntConstant(222L), - ErgoBox.nonMandatoryRegisters(5) -> AvlTreeConstant(treeData) - )) - lazy val byteCheck = checkEq(func[Box,Byte]("{ (x: Box) => x.R4[Byte].get }"))((x: Box) => x.R4[Byte].get) - lazy val shortCheck = checkEq(func[Box,Short]("{ (x: Box) => x.R5[Short].get }"))((x: Box) => x.R5[Short].get) - lazy val intCheck = checkEq(func[Box,Int]("{ (x: Box) => x.R6[Int].get }"))((x: Box) => x.R6[Int].get) - lazy val longCheck = checkEq(func[Box,Long]("{ (x: Box) => x.R7[Long].get }"))((x: Box) => x.R7[Long].get) - lazy val BigIntCheck = checkEq(func[Box,BigInt]("{ (x: Box) => x.R8[BigInt].get }"))((x: Box) => x.R8[BigInt].get) - byteCheck(s) - shortCheck(s) - intCheck(s) - longCheck(s) - BigIntCheck(s) - } - - property("PreHeader properties equivalence") { - val h = ctx.preHeader - val eq = EqualityChecker(h) - eq({ (x: PreHeader) => x.version })("{ (x: PreHeader) => x.version }") - eq({ (x: PreHeader) => x.parentId })("{ (x: PreHeader) => x.parentId }") - eq({ (x: PreHeader) => x.timestamp })("{ (x: PreHeader) => x.timestamp }") - eq({ (x: PreHeader) => x.nBits })("{ (x: PreHeader) => x.nBits }") - eq({ (x: PreHeader) => x.height })("{ (x: PreHeader) => x.height }") - eq({ (x: PreHeader) => x.minerPk })("{ (x: PreHeader) => x.minerPk }") - eq({ (x: PreHeader) => x.votes })("{ (x: PreHeader) => x.votes }") - } - - property("Header properties equivalence") { - val h = ctx.headers(0) - val eq = EqualityChecker(h) - eq({ (x: Header) => x.id })("{ (x: Header) => x.id }") - eq({ (x: Header) => x.version })("{ (x: Header) => x.version }") - eq({ (x: Header) => x.parentId })("{ (x: Header) => x.parentId }") - eq({ (x: Header) => x.ADProofsRoot})("{ (x: Header) => x.ADProofsRoot}") - eq({ (x: Header) => x.stateRoot })("{ (x: Header) => x.stateRoot }") - eq({ (x: Header) => x.transactionsRoot })("{ (x: Header) => x.transactionsRoot }") - eq({ (x: Header) => x.timestamp })("{ (x: Header) => x.timestamp }") - eq({ (x: Header) => x.nBits })("{ (x: Header) => x.nBits }") - eq({ (x: Header) => x.height })("{ (x: Header) => x.height }") - eq({ (x: Header) => x.extensionRoot })("{ (x: Header) => x.extensionRoot }") - eq({ (x: Header) => x.minerPk })("{ (x: Header) => x.minerPk }") - eq({ (x: Header) => x.powOnetimePk })("{ (x: Header) => x.powOnetimePk }") - eq({ (x: Header) => x.powNonce })("{ (x: Header) => x.powNonce }") - eq({ (x: Header) => x.powDistance })("{ (x: Header) => x.powDistance }") - eq({ (x: Header) => x.votes })("{ (x: Header) => x.votes }") - } - - property("Context properties equivalence") { - val eq = EqualityChecker(ctx) - eq({ (x: Context) => x.dataInputs })("{ (x: Context) => x.dataInputs }") - eq({ (x: Context) => x.dataInputs(0) })("{ (x: Context) => x.dataInputs(0) }") - eq({ (x: Context) => x.dataInputs(0).id })("{ (x: Context) => x.dataInputs(0).id }") - eq({ (x: Context) => x.preHeader })("{ (x: Context) => x.preHeader }") - eq({ (x: Context) => x.headers })("{ (x: Context) => x.headers }") - eq({ (x: Context) => x.OUTPUTS })("{ (x: Context) => x.OUTPUTS }") - eq({ (x: Context) => x.INPUTS })("{ (x: Context) => x.INPUTS }") - eq({ (x: Context) => x.HEIGHT })("{ (x: Context) => x.HEIGHT }") - eq({ (x: Context) => x.SELF })("{ (x: Context) => x.SELF }") - eq({ (x: Context) => x.INPUTS.map { (b: Box) => b.value } })("{ (x: Context) => x.INPUTS.map { (b: Box) => b.value } }") - eq({ (x: Context) => x.selfBoxIndex })("{ (x: Context) => x.selfBoxIndex }") - eq({ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed })("{ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }") - eq({ (x: Context) => x.minerPubKey })("{ (x: Context) => x.minerPubKey }") - eq({ (x: Context) => - x.INPUTS.map { (b: Box) => (b.value, b.value) } - })( - """{ (x: Context) => - | x.INPUTS.map { (b: Box) => (b.value, b.value) } - |}""".stripMargin - ) - - eq({ (x: Context) => - x.INPUTS.map { (b: Box) => - val pk = b.R4[Int].get - val value = longToByteArray(b.value) - (pk, value) - } - })( - """{ (x: Context) => - | x.INPUTS.map { (b: Box) => - | val pk = b.R4[Int].get - | val value = longToByteArray(b.value) - | (pk, value) - | } - |}""".stripMargin) - } - - property("getVar equivalence") { - val eq = checkEq(func[Int,Int]("{ (x: Int) => getVar[Int](2).get }", 2.toByte -> IntConstant(10))) { x => - 10 - } - eq(1) - } - - property("xorOf equivalence") { - val eq = checkEq(func[Coll[Boolean], Boolean]("{ (x: Coll[Boolean]) => xorOf(x) }")) { x => - xorOf(x) - } - forAll { x: Array[Boolean] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("LogicalNot equivalence") { - // TODO make a prefix method - val eq = checkEq(func[Boolean, Boolean]("{ (x: Boolean) => !x }")) { x => !x } - forAll { x: Boolean => eq(x) } - } - - property("Negation equivalence") { - // TODO make a prefix method - val negByte = checkEq(func[Byte, Byte]("{ (x: Byte) => -x }")) { (x: Byte) => (-x).toByte } - forAll { x: Byte => negByte(x) } - val negShort = checkEq(func[Short, Short]("{ (x: Short) => -x }")) { (x: Short) => (-x).toShort } - forAll { x: Short => negShort(x) } - val negInt = checkEq(func[Int, Int]("{ (x: Int) => -x }")) { (x: Int) => -x } - forAll { x: Int => negInt(x) } - val negLong = checkEq(func[Long, Long]("{ (x: Long) => -x }")) { (x: Long) => -x } - forAll { x: Long => negLong(x) } - } - - property("special.sigma.BigInt Negation equivalence") { - // TODO make negate() into a prefix method - val negBigInteger = checkEq(func[BigInt, BigInt]("{ (x: BigInt) => -x }")) { (x: BigInt) => x.negate() } - forAll { x: BigInt => negBigInteger(x) } - } - - property("BinXor(logical XOR) equivalence") { - val eq = checkEq(func[(Boolean, Boolean), Boolean]("{ (x: (Boolean, Boolean)) => x._1 ^ x._2 }")) { - x => x._1 ^ x._2 - } - forAll { x: (Boolean, Boolean) => eq(x) } - } - - property("BinXor(logical XOR) test") { - val eq = checkEq(func[(Int, Boolean), Boolean]("{ (x: (Int, Boolean)) => (x._1 == 0) ^ x._2 }")) { - x => (x._1 == 0) ^ x._2 - } - forAll { x: (Int, Boolean) => eq(x) } - } - - // TODO: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 - ignore("Box.getReg equivalence") { -// val eq = checkEq(func[Box, Int]("{ (x: Box) => x.getReg[Int](1).get }")) { x => x.getReg(1).get } -// forAll { x: Box => eq(x) } - } - - property("global functions equivalence") { - val n = SigmaDsl.BigInt(BigInteger.TEN) - val Global = SigmaDsl - - { - val eq = EqualityChecker(1) - eq({ (x: Int) => groupGenerator })("{ (x: Int) => groupGenerator }") - eq({ (x: Int) => Global.groupGenerator })("{ (x: Int) => Global.groupGenerator }") - } - - { - val eq = EqualityChecker(n) - eq({ (n: BigInt) => groupGenerator.exp(n) })("{ (n: BigInt) => groupGenerator.exp(n) }") - } - - { - val eq = checkEq(func[(Coll[Byte], Coll[Byte]), Coll[Byte]]( - "{ (x: (Coll[Byte], Coll[Byte])) => xor(x._1, x._2) }")) - { x => Global.xor(x._1, x._2) } - forAll(bytesGen, bytesGen) { (l, r) => - eq(Builder.DefaultCollBuilder.fromArray(l), Builder.DefaultCollBuilder.fromArray(r)) - } - } - } - - property("Coll methods equivalence") { - val coll = ctx.OUTPUTS - val eq = EqualityChecker(coll) - eq({ (x: Coll[Box]) => x.filter({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.filter({(b: Box) => b.value > 1 }) }") - eq({ (x: Coll[Box]) => x.flatMap({ (b: Box) => b.propositionBytes }) })("{ (x: Coll[Box]) => x.flatMap({(b: Box) => b.propositionBytes }) }") - eq({ (x: Coll[Box]) => x.zip(x) })("{ (x: Coll[Box]) => x.zip(x) }") - eq({ (x: Coll[Box]) => x.size })("{ (x: Coll[Box]) => x.size }") - eq({ (x: Coll[Box]) => x.indices })("{ (x: Coll[Box]) => x.indices }") - eq({ (x: Coll[Box]) => x.forall({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.forall({(b: Box) => b.value > 1 }) }") - eq({ (x: Coll[Box]) => x.exists({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.exists({(b: Box) => b.value > 1 }) }") - } - - property("Coll size method equivalnce") { - val eq = checkEq(func[Coll[Int],Int]("{ (x: Coll[Int]) => x.size }")){ x => - x.size - } - forAll { x: Array[Int] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("Coll patch method equivalnce") { - val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.patch(x._2._1, x._1, x._2._2) }")){ x => - x._1.patch(x._2._1, x._1, x._2._2) - } - forAll { x: Array[Int] => - whenever (x.size > 1) { - eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(x.size)) - } - } - } - - property("Coll updated method equivalnce") { - val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.updated(x._2._1, x._2._2) }")){ x => - x._1.updated(x._2._1, x._2._2) - } - forAll { x: (Array[Int], Int) => - val size = x._1.size - whenever (size > 1) { - val index = getRandomIndex(size) - eq(Builder.DefaultCollBuilder.fromArray(x._1), (index, x._2)) - } - } - } - - property("Coll updateMany method equivalnce") { - val eq = checkEq(func[(Coll[Int], (Coll[Int], Coll[Int])),Coll[Int]]("{ (x: (Coll[Int], (Coll[Int], Coll[Int]))) => x._1.updateMany(x._2._1, x._2._2) }")){ x => - x._1.updateMany(x._2._1, x._2._2) - } - forAll { x: (Array[Int], Int) => - val size = x._1.size - whenever (size > 1) { - val fromColl = Builder.DefaultCollBuilder.fromArray(x._1) - val indexColl = generateIndexColl(size) - eq(fromColl, (indexColl, fromColl.reverse.slice(0, indexColl.size))) - } - } - } - - // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Coll find method equivalnce") { - val eq = checkEq(func[Coll[Int],Option[Int]]("{ (x: Coll[Int]) => x.find({(v: Int) => v > 0})}")){ x => - x.find(v => v > 0) - } - forAll { x: Array[Int] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - // https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 - ignore("Coll bitwise methods equivalnce") { - val eq = checkEq(func[Coll[Boolean],Coll[Boolean]]("{ (x: Coll[Boolean]) => x >> 2 }")){ x => - if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl - } - forAll { x: Array[Boolean] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Coll diff methods equivalnce") { - val eq = checkEq(func[Coll[Int],Coll[Int]]("{ (x: Coll[Int]) => x.diff(x) }")){ x => - x.diff(x) - } - forAll { x: Array[Int] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("Coll fold method equivalence") { - val eq = checkEq(func[(Coll[Byte], Int),Int]("{ (x: (Coll[Byte], Int)) => x._1.fold(x._2, { (i1: Int, i2: Byte) => i1 + i2 }) }")) - { x => - x._1.foldLeft(x._2, { i: (Int, Byte) => i._1 + i._2 }) - } - val eqIndexOf = checkEq(func[(Coll[Byte], Byte),Int]("{ (x: (Coll[Byte], Byte)) => x._1.indexOf(x._2, 0) }")) - { x => - x._1.indexOf(x._2, 0) - } - forAll { x: (Array[Byte], Short, Byte) => - eq(Builder.DefaultCollBuilder.fromArray(x._1), x._2) - eqIndexOf(Builder.DefaultCollBuilder.fromArray(x._1), x._3) - } - } - - property("Coll indexOf method equivalnce") { - val eqIndexOf = checkEq(func[(Coll[Int], (Int, Int)),Int]("{ (x: (Coll[Int], (Int, Int))) => x._1.indexOf(x._2._1, x._2._2) }")) - { x => - x._1.indexOf(x._2._1, x._2._2) - } - forAll { x: (Array[Int], Int) => - eqIndexOf(Builder.DefaultCollBuilder.fromArray(x._1), (getRandomIndex(x._1.size), x._2)) - } - } - - property("Coll apply method equivalnce") { - val eqApply = checkEq(func[(Coll[Int], Int),Int]("{ (x: (Coll[Int], Int)) => x._1(x._2) }")) - { x => - x._1(x._2) - } - forAll { x: Array[Int] => - whenever (0 < x.size) { - eqApply(Builder.DefaultCollBuilder.fromArray(x), getRandomIndex(x.size)) - } - } - } - - property("Coll getOrElse method equivalnce") { - val eqGetOrElse = checkEq(func[(Coll[Int], (Int, Int)),Int]("{ (x: (Coll[Int], (Int, Int))) => x._1.getOrElse(x._2._1, x._2._2) }")) - { x => - x._1.getOrElse(x._2._1, x._2._2) - } - forAll { x: (Array[Int], (Int, Int)) => - eqGetOrElse(Builder.DefaultCollBuilder.fromArray(x._1), x._2) - } - } - - property("Tuple size method equivalence") { - val eq = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x.size }")) { x => 2 } - eq((-1, 1)) - } - - property("Tuple apply method equivalence") { - val eq1 = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x(0) }")) { x => -1 } - val eq2 = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x(1) }")) { x => 1 } - eq1((-1, 1)) - eq2((-1, 1)) - } - - property("Coll map method equivalnce") { - val eq = checkEq(func[Coll[Int],Coll[Int]]("{ (x: Coll[Int]) => x.map({ (v: Int) => v + 1 }) }")) - { x => - x.map(v => v + 1) - } - forAll { x: Array[Int] => - eq(Builder.DefaultCollBuilder.fromArray(x.filter(_ < Int.MaxValue))) - } - } - - property("Coll slice method equivalnce") { - val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.slice(x._2._1, x._2._2) }")) - { x => - x._1.slice(x._2._1, x._2._2) - } - forAll { x: Array[Int] => - val size = x.size - whenever (size > 0) { - eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(size)) - } - } - val arr = Array[Int](1, 2, 3, 4, 5) - eq(Builder.DefaultCollBuilder.fromArray(arr), (0, 2)) - } - - property("Coll append method equivalence") { - val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]( - """{ (x: (Coll[Int], (Int, Int))) => - |val sliced: Coll[Int] = x._1.slice(x._2._1, x._2._2) - |val toAppend: Coll[Int] = x._1 - |sliced.append(toAppend) - |}""".stripMargin)) - { x => - val sliced: Coll[Int] = x._1.slice(x._2._1, x._2._2) - val toAppend: Coll[Int] = x._1 - sliced.append(toAppend) - } - forAll { x: Array[Int] => - val size = x.size - whenever (size > 0) { - eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(size)) - } - } - } - - property("Option methods equivalence") { - val opt: Option[Long] = ctx.dataInputs(0).R0[Long] - val eq = EqualityChecker(opt) - eq({ (x: Option[Long]) => x.get })("{ (x: Option[Long]) => x.get }") - // TODO implement Option.isEmpty - // eq({ (x: Option[Long]) => x.isEmpty })("{ (x: Option[Long]) => x.isEmpty }") - eq({ (x: Option[Long]) => x.isDefined })("{ (x: Option[Long]) => x.isDefined }") - eq({ (x: Option[Long]) => x.getOrElse(1L) })("{ (x: Option[Long]) => x.getOrElse(1L) }") - eq({ (x: Option[Long]) => x.filter({ (v: Long) => v == 1} ) })("{ (x: Option[Long]) => x.filter({ (v: Long) => v == 1 }) }") - eq({ (x: Option[Long]) => x.map( (v: Long) => v + 1 ) })("{ (x: Option[Long]) => x.map({ (v: Long) => v + 1 }) }") - } - - // TODO implement Option.fold - ignore("Option fold method") { - val opt: Option[Long] = ctx.dataInputs(0).R0[Long] - val eq = EqualityChecker(opt) - eq({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => v + 1 ) })("{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }") - } - - property("Option fold workaround method") { - val opt: Option[Long] = ctx.dataInputs(0).R0[Long] - val eq = EqualityChecker(opt) - eq({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => v + 1 ) })( - """{(x: Option[Long]) => - | def f(opt: Long): Long = opt + 1 - | if (x.isDefined) f(x.get) else 5L - |}""".stripMargin) - } - - property("blake2b256, sha256 equivalence") { - val eqBlake2b256 = checkEq(func[Coll[Byte], Coll[Byte]]("{ (x: Coll[Byte]) => blake2b256(x) }")){ x => - blake2b256(x) - } - val eqSha256 = checkEq(func[Coll[Byte], Coll[Byte]]("{ (x: Coll[Byte]) => sha256(x) }")){ x => - sha256(x) - } - forAll { x: Array[Byte] => - Seq(eqBlake2b256, eqSha256).foreach(_(Builder.DefaultCollBuilder.fromArray(x))) - } - } - - property("print") { - println(ComplexityTableStat.complexityTableString) - } - - property("sigmaProp equivalence") { - lazy val eq = checkEq(func[Boolean, SigmaProp]("{ (x: Boolean) => sigmaProp(x) }")){ (x: Boolean) => - sigmaProp(x) - } - forAll { x: Boolean => eq(x) } - } - - property("atLeast equivalence") { - lazy val eq = checkEq(func[Coll[SigmaProp], SigmaProp]("{ (x: Coll[SigmaProp]) => atLeast(x.size - 1, x) }")){ (x: Coll[SigmaProp]) => - atLeast(x.size - 1, x) - } - forAll(arrayGen[SigmaProp].suchThat(_.length > 2)) { x: Array[SigmaProp] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("&& sigma equivalence") { - lazy val eq = checkEq(func[(SigmaProp, SigmaProp), SigmaProp]("{ (x:(SigmaProp, SigmaProp)) => x._1 && x._2 }")){ (x:(SigmaProp, SigmaProp)) => - x._1 && x._2 - } - forAll { x: (SigmaProp, SigmaProp) => - eq(x) - } - } - - property("|| sigma equivalence") { - lazy val eq = checkEq(func[(SigmaProp, SigmaProp), SigmaProp]("{ (x:(SigmaProp, SigmaProp)) => x._1 || x._2 }")){ (x:(SigmaProp, SigmaProp)) => - x._1 || x._2 - } - forAll { x: (SigmaProp, SigmaProp) => - eq(x) - } - } - - property("SigmaProp.propBytes equivalence") { - lazy val eq = checkEq(func[SigmaProp, Coll[Byte]]("{ (x: SigmaProp) => x.propBytes }")){ (x: SigmaProp) => - x.propBytes - } - forAll { x: SigmaProp => - eq(x) - } - } - - // TODO: implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 - ignore("allZK equivalence") { - lazy val eq = checkEq(func[Coll[SigmaProp], SigmaProp]("{ (x: Coll[SigmaProp]) => allZK(x) }")){ (x: Coll[SigmaProp]) => - allZK(x) - } - forAll(arrayGen[SigmaProp].suchThat(_.length > 2)) { x: Array[SigmaProp] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - // TODO: implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 - ignore("anyZK equivalence") { - lazy val eq = checkEq(func[Coll[SigmaProp], SigmaProp]("{ (x: Coll[SigmaProp]) => anyZK(x) }")){ (x: Coll[SigmaProp]) => - anyZK(x) - } - forAll(arrayGen[SigmaProp].suchThat(_.length > 2)) { x: Array[SigmaProp] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("allOf equivalence") { - lazy val eq = checkEq(func[Coll[Boolean], Boolean]("{ (x: Coll[Boolean]) => allOf(x) }")){ (x: Coll[Boolean]) => - allOf(x) - } - forAll(arrayGen[Boolean].suchThat(_.length > 2)) { x: Array[Boolean] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("anyOf equivalence") { - lazy val eq = checkEq(func[Coll[Boolean], Boolean]("{ (x: Coll[Boolean]) => anyOf(x) }")){ (x: Coll[Boolean]) => - anyOf(x) - } - forAll(arrayGen[Boolean].suchThat(_.length > 2)) { x: Array[Boolean] => - eq(Builder.DefaultCollBuilder.fromArray(x)) - } - } - - property("proveDlog equivalence") { - val eq = EqualityChecker(SigmaDsl.groupGenerator) - eq({ (x: GroupElement) => proveDlog(x) })("{ (x: GroupElement) => proveDlog(x) }") - } - - property("proveDHTuple equivalence") { - val eq = EqualityChecker(SigmaDsl.groupGenerator) - eq({ (x: GroupElement) => proveDHTuple(x, x, x, x) })("{ (x: GroupElement) => proveDHTuple(x, x, x, x) }") - } - - property("&& boolean equivalence") { - lazy val eq = checkEq(func[(Boolean, Boolean), Boolean]("{ (x:(Boolean, Boolean)) => x._1 && x._2 }")){ (x:(Boolean, Boolean)) => - x._1 && x._2 - } - forAll { x: (Boolean, Boolean) => eq(x) } - } - - property("|| boolean equivalence") { - lazy val eq = checkEq(func[(Boolean, Boolean), Boolean]("{ (x:(Boolean, Boolean)) => x._1 || x._2 }")){ (x:(Boolean, Boolean)) => - x._1 || x._2 - } - forAll { x: (Boolean, Boolean) => eq(x) } - } - - property("lazy || and && boolean equivalence") { - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => x || (1 / 0 == 1) }")) - { (x: Boolean) => x || (1 / 0 == 1) }(true) - - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => x && (1 / 0 == 1) }")) - { (x: Boolean) => x && (1 / 0 == 1) }(false) - - // nested - - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => x && (x || (1 / 0 == 1)) }")) - { (x: Boolean) => x && (x || (1 / 0 == 1)) }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => x && (x && (x || (1 / 0 == 1))) }")) - { (x: Boolean) => x && (x && (x || (1 / 0 == 1))) }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))) }")) - { (x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))) }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)) }")) - { (x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)) }(true) - - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => (x || (1 / 0 == 1)) && x }")) - { (x: Boolean) => (x || (1 / 0 == 1)) && x }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)) }")) - { (x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)) }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (x || (1 / 0 == 1)) }")) - { (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (x || (1 / 0 == 1)) }(true) - checkEq(func[Boolean, Boolean]( - "{ (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (!(!x && (1 / 0 == 1)) || (1 / 0 == 1)) }")) - { (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (!(!x && (1 / 0 == 1)) || (1 / 0 == 1)) }(true) - } -} diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala new file mode 100644 index 0000000000..1d0e4a8c0a --- /dev/null +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -0,0 +1,432 @@ +package special.sigma + +import org.scalatest.prop.PropertyChecks +import sigmastate.interpreter.Interpreter.ScriptEnv +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.{PropSpec, Matchers} + +import scala.util.{Success, Failure, Try} +import sigmastate.Values.SValue +import scalan.RType +import org.ergoplatform.dsl.{SigmaContractSyntax, TestContractSpec} +import sigmastate.eval.{IRContext, SigmaDsl} +import sigmastate.helpers.SigmaPPrint +import special.collection.Coll + +import scala.math.Ordering +import scala.reflect.ClassTag + +class SigmaDslTesting extends PropSpec + with PropertyChecks + with Matchers + with SigmaTestingData with SigmaContractSyntax + with SigmaTypeGens { suite => + + lazy val spec = TestContractSpec(suite)(new TestingIRContext) + + override def contractEnv: ScriptEnv = Map() + + implicit lazy val IR = new TestingIRContext { + override val okPrintEvaluatedEntries: Boolean = false + override val okMeasureOperationTime: Boolean = true + } + + def checkEq[A,B](f: A => B)(g: A => B): A => Try[B] = { x: A => + val b1 = Try(f(x)); val b2 = Try(g(x)) + (b1, b2) match { + case (res @ Success(b1), Success(b2)) => + assert(b1 == b2) + res + case (res @ Failure(t1), Failure(t2)) => + val c1 = rootCause(t1).getClass + val c2 = rootCause(t2).getClass + c1 shouldBe c2 + res + case _ => + val cause = if (b1.isFailure) + rootCause(b1.asInstanceOf[Failure[_]].exception) + else + rootCause(b2.asInstanceOf[Failure[_]].exception) + + sys.error( + s"""Should succeed with the same value or fail with the same exception, but was: + |First result: $b1 + |Second result: $b2 + |Root cause: $cause + |""".stripMargin) + } + + } + + def checkEq2[A,B,R](f: (A, B) => R)(g: (A, B) => R): (A,B) => Unit = { (x: A, y: B) => + val r1 = f(x, y); val r2 = g(x, y) + assert(r1.getClass == r2.getClass) + assert(r1 == r2) + } + + def getArrayIndex(len: Int): Int = { + val index = Gen.choose(0, len - 1) + index.sample.get + } + + /** Generate indices for an array of a given length. + * @return unordered array of indices with possibly repeated elements + */ + def genIndices(arrLength: Int): Gen[Array[Int]] = for { + nIndexes <- Gen.choose(0, arrLength) + indices <- Gen.containerOfN[Array, Int](nIndexes, Gen.choose(0, arrLength - 1)) + } yield indices + + + case class EqualityChecker[T: RType](obj: T) { + def apply[R: RType](dslFunc: T => R)(script: String) = + checkEq(func[T, R](script))(dslFunc)(obj) + } + + /** Type of the language feature to be tested. */ + sealed trait FeatureType + case object ExistingFeature extends FeatureType + case object AddedFeature extends FeatureType + + val LogScriptDefault: Boolean = false + + /** Test case descriptor of the language feature. + * + * @param featureType type of the language feature + * @param scalaFunc feature semantic given by scala code + * @param expectedExpr expression which represents the test case code + * @param oldImpl function that executes the feature using v3 interpreter implementation + * @param newImpl function that executes the feature using v4 interpreter implementation + */ + case class FeatureTest[A, B](featureType: FeatureType, + script: String, + scalaFunc: A => B, + expectedExpr: Option[SValue], + oldImpl: () => CompiledFunc[A, B], + newImpl: () => CompiledFunc[A, B], + printExpectedExpr: Boolean = true, + logScript: Boolean = LogScriptDefault + ) { + def printExpectedExprOff = copy(printExpectedExpr = false) + + /** Called to print test case expression (when it is not given). + * Can be used to create regression test cases. */ + def printSuggestion(cf: CompiledFunc[_,_]): Unit = { + print(s"No expectedExpr for ") + SigmaPPrint.pprintln(cf.script, height = 150) + print("Use ") + SigmaPPrint.pprintln(cf.expr, height = 150) + println() + } + + /** Checks the result of the front-end compiler against expectedExpr. + * Used to catch regression errors in front-end compiler. + */ + def checkExpectedExprIn(cf: CompiledFunc[_,_]): Boolean = { + expectedExpr match { + case Some(e) => + if (cf.expr != e) { + printSuggestion(cf) + cf.expr shouldBe e + } + case None if printExpectedExpr => + printSuggestion(cf) + } + true + } + + /** v3 implementation*/ + private var _oldF: CompiledFunc[A, B] = null + def oldF: CompiledFunc[A, B] = { + if (_oldF == null) { + _oldF = oldImpl() + checkExpectedExprIn(_oldF) + } + _oldF + } + + /** v4 implementation*/ + private var _newF: CompiledFunc[A, B] = null + def newF: CompiledFunc[A, B] = { + if (_newF == null) { + _newF = newImpl() + checkExpectedExprIn(_newF) + } + _newF + } + + /** Depending on the featureType compares the old and new implementations against + * semantic function (scalaFunc) on the given input. + * @param input data which is used to execute feature + * @return result of feature execution */ + def checkEquality(input: A, logInputOutput: Boolean = false): Try[B] = featureType match { + case ExistingFeature => + // check both implementations with Scala semantic + val oldRes = checkEq(scalaFunc)(oldF)(input) + + if (!(newImpl eq oldImpl)) { + val newRes = checkEq(scalaFunc)(newF)(input) + newRes shouldBe oldRes + } + if (logInputOutput) + println(s"(${SigmaPPrint(input, height = 550, width = 150)}, ${SigmaPPrint(oldRes, height = 550, width = 150)}),${if (logScript) " // " + script else ""}") + oldRes + case AddedFeature => + val oldRes = Try(oldF(input)) + oldRes.isFailure shouldBe true + if (!(newImpl eq oldImpl)) { + val newRes = checkEq(scalaFunc)(newF)(input) + newRes shouldBe oldRes + } + oldRes + } + + /** Depending on the featureType compares the old and new implementations against + * semantic function (scalaFunc) on the given input, also checking the given expected result. + */ + def checkExpected(input: A, expectedResult: B): Unit = { + featureType match { + case ExistingFeature => + // check both implementations with Scala semantic + val oldRes = checkEq(scalaFunc)(oldF)(input) + oldRes.get shouldBe expectedResult + + if (!(newImpl eq oldImpl)) { + val newRes = checkEq(scalaFunc)(newF)(input) + newRes.get shouldBe expectedResult + } + + case AddedFeature => + Try(oldF(input)).isFailure shouldBe true + if (!(newImpl eq oldImpl)) { + val newRes = checkEq(scalaFunc)(newF)(input) + newRes.get shouldBe expectedResult + } + } + } + + } + + /** Describes existing language feature which should be equally supported in both v3 and + * v4 of the language. + * + * @param scalaFunc semantic function which defines expected behavior of the given script + * @param script the script to be tested against semantic function + * @param expectedExpr expected ErgoTree expression which corresponds to the given script + * @return feature test descriptor object which can be used to execute this test case in + * various ways + */ + def existingFeature[A: RType, B: RType] + (scalaFunc: A => B, script: String, expectedExpr: SValue = null) + (implicit IR: IRContext): FeatureTest[A, B] = { + val oldImpl = () => func[A, B](script) + val newImpl = oldImpl // TODO HF: use actual new implementation here + FeatureTest(ExistingFeature, script, scalaFunc, Option(expectedExpr), oldImpl, newImpl) + } + + /** Describes a NEW language feature which must NOT be supported in v3 and + * must BE supported in v4 of the language. + * + * @param scalaFunc semantic function which defines expected behavior of the given script + * @param script the script to be tested against semantic function + * @param expectedExpr expected ErgoTree expression which corresponds to the given script + * @return feature test descriptor object which can be used to execute this test case in + * various ways + */ + def newFeature[A: RType, B: RType] + (scalaFunc: A => B, script: String, expectedExpr: SValue = null) + (implicit IR: IRContext): FeatureTest[A, B] = { + val oldImpl = () => func[A, B](script) + val newImpl = oldImpl // TODO HF: use actual new implementation here + FeatureTest(AddedFeature, script, scalaFunc, Option(expectedExpr), oldImpl, newImpl) + } + + val contextGen: Gen[Context] = ergoLikeContextGen.map(c => c.toSigmaContext(IR, false)) + implicit val arbContext = Arbitrary(contextGen) + + /** NOTE, this should be `def` to allow overriding of generatorDrivenConfig in derived Spec classes. */ + def DefaultMinSuccessful: MinSuccessful = MinSuccessful(generatorDrivenConfig.minSuccessful) + + val PrintTestCasesDefault: Boolean = false + val FailOnTestVectorsDefault: Boolean = true + + /** Test the given test cases with expected results (aka test vectors). + * NOTE, is some cases (such as Context, Box, etc) sample generation is time consuming, so it + * makes sense to factor it out. + * @param preGeneratedSamples optional pre-generated samples to reduce execution time + */ + def testCases[A: Ordering : Arbitrary : ClassTag, B] + (cases: Seq[(A, Try[B])], + f: FeatureTest[A, B], + printTestCases: Boolean = PrintTestCasesDefault, + failOnTestVectors: Boolean = FailOnTestVectorsDefault, + preGeneratedSamples: Option[Seq[A]] = None): Unit = { + + val table = Table(("x", "y"), cases:_*) + forAll(table) { (x: A, expectedRes: Try[B]) => + val res = f.checkEquality(x, printTestCases) + + // TODO HF: remove this `if` once newImpl is implemented + if (f.featureType == ExistingFeature) { + (res, expectedRes) match { + case (Failure(exception), Failure(expectedException)) => + exception.getClass shouldBe expectedException.getClass + case _ => + if (failOnTestVectors) { + assertResult(expectedRes, s"Actual: ${SigmaPPrint(res, height = 150).plainText}")(res) + } + else { + if (expectedRes != res) { + print("\nSuggested Expected Result: ") + SigmaPPrint.pprintln(res, height = 150) + } + } + } + } + } + preGeneratedSamples match { + case Some(samples) => + test(samples, f, printTestCases) + case None => + test(f, printTestCases) + } + } + + /** Generate samples in sorted order. + * @param gen generator to be used for sample generation + * @param config generation configuration + * @return array-backed ordered sequence of samples + */ + def genSamples[A: Ordering: ClassTag](gen: Gen[A], config: PropertyCheckConfigParam): Seq[A] = { + implicit val arb = Arbitrary(gen) + genSamples[A](config) + } + + /** Generate samples in sorted order. + * @param config generation configuration + * @return array-backed ordered sequence of samples + */ + def genSamples[A: Arbitrary: Ordering: ClassTag](config: PropertyCheckConfigParam): Seq[A] = { + val inputs = scala.collection.mutable.ArrayBuilder.make[A]() + forAll(config) { (x: A) => + inputs += x + } + inputs.result().sorted + } + + def test[A: Ordering : ClassTag, B] + (samples: Seq[A], + f: FeatureTest[A, B], + printTestCases: Boolean): Unit = { + + // then tests them in the sorted order, this will output a nice log of test cases + samples.foreach { x => + f.checkEquality(x, printTestCases) + } + } + + def test[A: Ordering : ClassTag, B](samples: Seq[A], f: FeatureTest[A, B]): Unit = { + test(samples, f, PrintTestCasesDefault) + } + + def test[A: Arbitrary : Ordering : ClassTag, B] + (f: FeatureTest[A, B], + printTestCases: Boolean = PrintTestCasesDefault): Unit = { + // first generate all test inputs + val samples = genSamples[A](DefaultMinSuccessful) + // then test them + test(samples, f, printTestCases) + } + + trait GroupElementOrdering extends Ordering[GroupElement] { + /** Compares `x: ECPoint` string representation with `y: ECPoint` string for order. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: GroupElement, y: GroupElement) = { + SigmaDsl.toECPoint(x).toString.compareTo(SigmaDsl.toECPoint(y).toString) + } + } + implicit object GroupElementOrdering extends GroupElementOrdering + + trait AvlTreeOrdering extends Ordering[AvlTree] { + /** Compares this `x: AvlTree` string representation with `y: AvlTree` string for order. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: AvlTree, y: AvlTree) = { + x.toString.compareTo(y.toString) + } + } + implicit object AvlTreeOrdering extends AvlTreeOrdering + + class CollOrdering[T: Ordering] extends Ordering[Coll[T]] { + implicit val O = implicitly[Ordering[Iterable[T]]] + + /** Compares this `x: Coll` with `y: Coll` using Ordering for underlying Array. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: Coll[T], y: Coll[T]) = { + O.compare(x.toArray, y.toArray) + } + } + implicit def collOrdering[T: Ordering]: Ordering[Coll[T]] = new CollOrdering[T] + + trait BoxOrdering extends Ordering[Box] { + /** Compares this `x: Box` string representation with `y: Box` string for order. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: Box, y: Box) = { + x.toString.compareTo(y.toString) + } + } + implicit object BoxOrdering extends BoxOrdering + + trait PreHeaderOrdering extends Ordering[PreHeader] { + /** Compares this `x: PreHeader` with `y: PreHeader` using block height. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: PreHeader, y: PreHeader) = { + Ordering.Int.compare(x.height, y.height) + } + } + implicit object PreHeaderOrdering extends PreHeaderOrdering + + trait HeaderOrdering extends Ordering[Header] { + /** Compares this `x: Header` with `y: Header` using block height. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: Header, y: Header) = { + Ordering.Int.compare(x.height, y.height) + } + } + implicit object HeaderOrdering extends HeaderOrdering + + trait ContextOrdering extends Ordering[Context] { + val O = Ordering[(Int, Coll[Byte])] + + /** Compares this `x: Context` with `y: Context` using block height and SELF.id. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: Context, y: Context) = { + O.compare((x.HEIGHT, x.SELF.id), (y.HEIGHT, y.SELF.id)) + } + } + implicit object ContextOrdering extends ContextOrdering + + trait SigmaPropOrdering extends Ordering[SigmaProp] { + /** Compares this `x: SigmaProp` with `y: SigmaProp` using string representation. + * @returns a negative integer, zero, or a positive integer as the + * `x` is less than, equal to, or greater than `y`. + */ + def compare(x: SigmaProp, y: SigmaProp) = { + x.toString.compareTo(y.toString) + } + } + implicit object SigmaPropOrdering extends SigmaPropOrdering +} diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index 6eab01e559..da555c8165 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -10,26 +10,49 @@ import org.scalacheck.{Arbitrary, Gen} import sigmastate.helpers.SigmaTestingCommons import sigmastate.eval._ import sigmastate.eval.Extensions._ -import org.ergoplatform.{DataInput, ErgoBox, ErgoLikeContext, ErgoLikeTransaction} -import scorex.crypto.hash.{Blake2b256, Digest32} +import org.ergoplatform.{ErgoLikeContext, DataInput, ErgoLikeTransaction, ErgoBox} +import org.scalacheck.util.Buildable +import scalan.RType +import scorex.crypto.hash.{Digest32, Blake2b256} import scorex.crypto.authds.{ADKey, ADValue} +import special.collection.Coll trait SigmaTestingData extends SigmaTestingCommons with SigmaTypeGens { - val bytesGen: Gen[Array[Byte]] = containerOfN[Array, Byte](100, Arbitrary.arbByte.arbitrary) + def collOfN[T: RType: Arbitrary](n: Int)(implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { + implicit val g: Gen[T] = Arbitrary.arbitrary[T] + containerOfN[Array, T](n, g).map(Colls.fromArray(_)) + } + + val bytesGen: Gen[Array[Byte]] = for { + len <- Gen.choose(0, 100) + arr <- containerOfN[Array, Byte](len, Arbitrary.arbByte.arbitrary) + } yield arr + val bytesCollGen = bytesGen.map(Colls.fromArray(_)) + val intsCollGen = arrayGen[Int].map(Colls.fromArray(_)) + implicit val arbBytes = Arbitrary(bytesCollGen) - val keyCollGen = bytesCollGen.map(_.slice(0, 32)) + implicit val arbInts = Arbitrary(intsCollGen) + + val keyCollGen = collOfN[Byte](32) + import org.ergoplatform.dsl.AvlTreeHelpers._ + def createAvlTreeAndProver(entries: (Coll[Byte], Coll[Byte])*) = { + val kvs = entries.map { case (k,v) => ADKey @@ k.toArray -> ADValue @@ v.toArray} + val res = createAvlTree(AvlTreeFlags.AllOperationsAllowed, kvs:_*) + res + } + protected def sampleAvlProver = { val key = keyCollGen.sample.get val value = bytesCollGen.sample.get - val (_, avlProver) = createAvlTree(AvlTreeFlags.AllOperationsAllowed, ADKey @@ key.toArray -> ADValue @@ value.toArray) - (key, value, avlProver) + val (tree, prover) = createAvlTreeAndProver(key -> value) + (key, value, tree, prover) } protected def sampleAvlTree: AvlTree = { - val (key, _, avlProver) = sampleAvlProver + val (_, _, _, avlProver) = sampleAvlProver val digest = avlProver.digest.toColl val tree = SigmaDsl.avlTree(AvlTreeFlags.ReadOnly.serializeToByte, digest, 32, None) tree @@ -95,7 +118,7 @@ trait SigmaTestingData extends SigmaTestingCommons with SigmaTypeGens { boxesToSpend = IndexedSeq(inBox), spendingTransaction = new ErgoLikeTransaction(IndexedSeq(), IndexedSeq(DataInput(dataBox.id)), IndexedSeq(outBox)), selfIndex = 0, headers = headers, preHeader = preHeader, dataBoxes = IndexedSeq(dataBox), - extension = ContextExtension.empty, + extension = ContextExtension(Map(2.toByte -> IntConstant(10))), validationSettings = ValidationRules.currentSettings, costLimit = ScriptCostLimit.value, initCost = 0L) }