Message Boards Message Boards

More dissent about the NFT "movement"

Posted 3 years ago

autoglyph remix

The purpose of this memo is to take generative code, transliterate, and run it from first principles in mathematica. This is all done in good fun, so we will also throw in extra symmetry analysis for free.

At least one of the NFT projects on opensea, namely Larva Lab's Autoglyphys, shows exemplary rigor with asset data on chain. The Autoglyphs are valuable works of art comparable with hand-drawn designs from geometric tattoo artists, and the original authors are also doing interesting things with robotic printing. Thus we have a motive to recreate the autoglyphs locally, and to study their properties. Examining the contract closely enough, we find a few simple functions that do most of the work, and one confusing but critical line:

uint a = uint(uint160(keccak256(abi.encodePacked(idToSeed[id]))));

Don't ask me for all the details. I calculated (with an old compiler), for special example AG#308, the hidden value:

a308 = 804491777491045640850919148972519969740151830770;
Mod[a308, 83]
Out[]=14

Since $14<20$, indeed we are working in scheme $1$ with lexicon $\{X,/,\backslash \}$. We need a little bit of code, which essentially computes a multiplication table with variable symmetry and bad truncation of 256-bit integers (which seems to add entropy and structure):

ONE = 4294967296;
SIZE = 64; HALFSIZE = 32;

y[ind_, aVal_] := With[{y0 = 2*(ind - HALFSIZE) + 1},
  Switch[Mod[aVal, 3], 0, y0, 1, -y0, 2, Abs[y0]]*aVal]

x[ind_, aVal_] := With[{x0 = 2*(ind - HALFSIZE) + 1},
  Switch[Mod[aVal, 2], 0, x0*aVal, 1, Abs[x0]*aVal]]

v[x_, y_, mod_] := With[{v0 = Mod[Floor[
      If[# > 2^255 - 1, Mod[(# - 2^256)/ONE + 1, 2^256],
         #/ONE] &@Mod[x*y, 2^256]], mod]}, If[v0 < 5, v0, 0]]

AutoGlyphM[aVal_] := With[{mod = Mod[aVal, 11] + 5},
  Outer[v[x[#2, a308], y[#1, a308], mod] &,
   Range[0, SIZE - 1], Range[0, SIZE - 1], 1]]

TakeAutoGlyphType1[ind_] := ReplaceAll[Characters[StringSplit[Import[
     "https://www.larvalabs.com/autoglyphs/rawglyph?index="
      <> ToString[ind]], " "]], {"." -> 0, "X" -> 1, "/" -> 2, "\\" -> 3}]

This code seems to work for example $308$ because the difference between data and compute is an null array interspersed with $4$ values, but in this spec. $4$ is an extra null symbol.

dat1 = AutoGlyphM[a308];
dat2 = TakeAutoGlyphType1[308];
ArrayPlot /@ {dat1, dat2}
ArrayPlot[dat1 - dat2]

compare

diff

So instead of wasting the $4$ data, we will map it to an extra $O$ symbol. Then we can affix a seal for the new moon of December, when a cubist $\emptyset$ and a mutation of the OX glyph could both be taken from the proofs of $\large{+}$ my?T¿Ry $\large{+}\large{+} 4\rightarrow O$.

Also, I made two different view functions, one vector, one array.

 t0 = 1/8;

HashFill[or_] :=  Polygon[or + # & /@ Tuples[{1/2, -1/2}, 2][[{1, 2, 4, 3}]]];
EmptyFill[or_] := {};
DashLine[or_] :=  Polygon[{or - {1/2, t0}, or - {1/2, -t0}, or + {1/2, t0}, 
    or + {1/2, -t0}}];
PipeLine[or_] := Polygon[{or - {t0, 1/2}, or - {-t0, 1/2}, or + {t0, 1/2}, 
    or + {-t0, 1/2}}];
BSlash[or_] := Polygon[{or + {-1/2, 1/2} + {0, -t0 Sqrt[2]}, or + {-1/2, 1/2},
    or + {-1/2, 1/2} + {t0 Sqrt[2], 0}, 
    or + {1/2, -1/2} + {0, t0 Sqrt[2]},
    or + {1/2, -1/2}, or + {1/2, -1/2} + {-t0 Sqrt[2], 0}}];
FSlash[or_] := Polygon[{or + {-1/2, -1/2} + {0, t0 Sqrt[2]}, or + {-1/2, -1/2},
    or + {-1/2, -1/2} + {t0 Sqrt[2], 0}, 
    or + {1/2, 1/2} + {0, -t0 Sqrt[2]},
    or + {1/2, 1/2}, or + {1/2, 1/2} + {-t0 Sqrt[2], 0}}];

CrossLines[or_] := {FSlash[or], BSlash[or]};
PlusLines[or_] := {DashLine[or], PipeLine[or]};
Circ[or_] := {Black, Disk[or, 1/2], White, Disk[or, 1/2 - 2 t0]};

MHashFill = Table[1, {i, 1, 3}, {j, 1, 3}];
MEmptyFill = Mod[MHashFill + 1, 2];
MDashLine = Table[#, {i, 1, 3}] & /@ {0, 1, 0};
MPipeLine = Transpose[MDashLine];
MBSlash = IdentityMatrix[3];
MFSlash = Reverse[MBSlash];
MCirc = MapThread[Mod[#1 + #2, 2] &, {MDashLine, MPipeLine}, 2];
MPlus = ReplacePart[MCirc, {2, 2} -> 1];
MCross = ReplacePart[
   MapThread[Mod[#1 + #2, 2] &, {MFSlash, MBSlash}, 2], {2, 2} -> 1];

Hexagram[or_, code_] :=  Map[or + # &, Position[ReplacePart[Table[If[OddQ[i],
      If[MemberQ[Complement[Range[15], {2, 14}], j], 1, 0],
      If[MemberQ[{1, 15}, j], 1, 0]], {i, 15}, {j, 15}],
    Join[{{1, 2} -> 1, {1, 14} -> 1, {15, 2} -> 1, {15, 14} -> 1},
     Flatten[
      Outer[{1 + 2 #1, #2} -> 0 &, 
       Position[code, 0][[All, 1]], {7, 8, 9}]]
     ]], 1]]

Once again, the view code is messy, and takes more space than compute, but recall that view code doesn't need to be on chain. Nevertheless, such code is valuable since it makes data easier to understand and appreciate

Show[Graphics[  MapIndexed[ 
   Apply[#1 /. {0 -> EmptyFill, 1 -> CrossLines, 2 -> FSlash, 
       3 -> BSlash, 4 -> Circ },
     {Reverse[#2] {1, -1}}] &, dat1, {2}], ImageSize -> 64*8], 
 Graphics[{Darker@Red, EdgeForm[Darker@Red],
   HashFill[Reverse@# {1, -1}] & /@ 
    Hexagram[{3, 3}, {0, 0, 1, 0, 1, 0}]}], ImageSize -> 192*4]

vector small

Image[ReplacePart[ Mod[Plus[ ArrayFlatten[
      dat1 /. {0 -> MEmptyFill, 1 -> MCross, 2 -> MFSlash, 
        3 -> MBSlash, 4 -> MCirc }], 1], 2], # -> 2 & /@ 
    Hexagram[{20, 21}, {0, 0, 1, 0, 1, 0}]] /. {0 -> {0, 0, 0}, 
   1 -> {1, 1, 1}, 2 -> {0.8, 0, 0}}, ImageSize -> 192*4]

array small

Now we can see quite obviously transpose symmetry, and note that it probably follows from the truth value

And[Mod[a308, 3] == 0, Mod[a308, 2] == 0]
Out[]=True

We are not too enthusiastic about proving this conjecture, but would rather strengthen it through more data analysis. What are the secret code values for the other $511$ glyphs? Do modulus values of these codes predict symmetry type? We will return shortly with more answers.

POSTED BY: Brad Klee
2 Replies
Posted 3 years ago

That I did not post the original set of $512$ secret keys leaves something to desire, and makes further study difficult for students. To work around this issue I've prepared a similar INT160 distribution for test cases only (unfortunately half the values were tared to zero in transit).

aVals = ToExpression[StringSplit[
    Import["~/mathfun/AutoGlyphs/hiddenVals.txt"], "\n"]];

aVals2 = DeleteCases[ToExpression /@ StringSplit[
     (*Import["~/OpenAssets/Seeds/AutglyphSeeds.txt"]*)
          Import["https://0x0.st/-FJL.txt"], "\n"], 0];

GraphicsRow[Show[#, ImageSize -> 250] & /@ {
   Histogram[Log[2, #] & /@ {aVals2, aVals}],
   ListPlot[Tally[Mod[Floor[{#, #/2}], 2] & /@ #][[All, 2]] & /@ {aVals, 
      aVals2}, PlotRange -> 160],
   ListPlot[Tally[Mod[Floor[{#, #/2, #/4}], 2] & /@ #][[All, 2]] & /@ {aVals, 
      aVals2}, PlotRange -> 100]}]

stats

First is digit count, and second two show divisibility by $2$ and $4$. Why are the second two graphs important? Well, when investigating symmetry projections of the Autoglyphs we noticed needless excess using mod 2 and mod 3. This led us to believe that the source code could be simplified to using only mod 2, and we decided to avoid Collatz conjecture by choosing Floor of int divide by two (or four). Even distribution across symmetry classes is all that we were really looking for anyways.

Does that mean we are going to three dimensions? Of course, yes.

Here it is, the dimensional generalization to 3D autoglyphs for the voxelverse, and I would think they should be multicolored so as to be more attractive to speculative viewers in an online environment such as an art gallery or a digital concert venue. Here is the modified / simplified code:

ONE = 4294967296;
SIZE = 64; HALFSIZE = 32;

xyz[ind_, aVal_] := With[{x0 = 2*(ind - HALFSIZE) + 1},
  Switch[Mod[aVal, 2], 0, x0, 1, Abs[x0]]*aVal]

v[x_, y_, z_, mod_] := With[{v0 = Mod[Floor[
      If[# > 2^255 - 1, Mod[(# - 2^256)/ONE + 1, 2^256],
         #/ONE] &@Mod[x*y*z, 2^256]], mod]},
  If[v0 < 5, v0, 0]]

AutoGlyphM[aVal_] := With[{mod = Mod[aVal, 11] + 5},
  Outer[v[xyz[#1, aVal], xyz[#2, Floor[aVal/2]],
     xyz[#3, Floor[aVal/4]], mod] &,
   Range[0, SIZE - 1], Range[0, SIZE - 1], Range[0, SIZE - 1], 1]]

SymType = Mod[Floor[{#, #/2, #/4}], 2] & /@ aVals2;
ref = Join[{{0, 0, 0}}, IdentityMatrix[3], 
   Mod[IdentityMatrix[3] + 1, 2], {{1, 1, 1}}];
SortInds = Position[SymType, #][[All, 1]] & /@ ref;

CharacteristicSeeds = aVals2[[SortInds[[All, 1]]]]

Out[]= {
1068947993658975122157353753616942840246806141024, 
875695379343193424823445348694680652740201185481, 
495672220319428784155078550997997180172599139322, 
92962701827533250346487117218119002306342033068, 
807669444641696051849891548387372924883122769942, 
58854672879382211919039871200182349957445942893, 
207752697509440412973534330689716944749507327427, 
751209551927174784144601640564624173088665128391}

These eight seeds should give us examples of the eight symmetry types. Next we compute the data and check cross sections:

AbsoluteTiming[AGTests = AutoGlyphM[#] & /@ CharacteristicSeeds;]

Out[]= 1/2 min.    

colRules = {1 -> {1, 0, 0}, 2 -> {0, 1, 0}, 3 -> {0, 0, 1}, 
   4 -> {1, 1, 0}, 0 -> {1, 1, 1} };

CrossSections[AGMat_, ind_] := Show[
    Image[AGMat[[Sequence @@ #]] /. colRules],
    ImageSize -> 64 4] & /@ ReplaceAll[
   IdentityMatrix[3], {0 -> All, 1 -> ind}]

GraphicsGrid[CrossSections[#, 32] & /@ AGTests]

new glyphs

Take a second to visually analyze the symmetry and try and figure out what's going on. Are we sure that validation would pass for the entire set?

Even though these plots look kinda google from four color scheme, they are still intriguing and not entirely "cloned". What happened to the one that looked kind of Tibetan on the central cut? Oh yea, I changed the random seeds, so it may never be found again, but here it is for reference purposes:

lost animation "Another seed gone missing"

We need to choose a replacement, maybe number 3 because it is the most dense:

Total[Flatten[AGTests[[#]] /. x_Integer /; x > 0 :> 1]] & /@ Range[8]
Position[%, Max[%]][[1, 1]]
Out[]={116500, 75460, 137548, 86560, 87796, 74840, 104620, 116072}
Out[]=3

But nah, if it's up to me, I would go for #5 because it has a nice central green plus and interesting Red / Blue polarization features. Here's the full animation going through cross sections:

glist = GraphicsRow[CrossSections[AGTests[[5]], #]] & /@ Range[64];
ListAnimate[glist]

another animation

Okay let's see the output in three-dimensional exploded view, just like it was in the days of the icosahedral quasicrystal plots. Need to do a little boundary trimming and careful on the central data:

green trip plus

There it is! The green trip. plus! That is the sign that like literally e.b. was looking for when they starting chanting last year or a couple years ago. If you don't want all these colors and floating cubes? Well, we can go for dense option 3 and paint the entire exterior an enigmatic shade of gray:

gray

When I see this one, I'm like "Oh no, it's the Borg again. Don't be fooled by seven of nine, she's probably playing a numbers game with her ancestors in the source code." The advantage of this plot is that it has armour. No telling what's inside unless you crack it open... So that's it for me on this thread. If you have a fav. choice from 256, drop me a line telling reason why, preferably with an original render.

POSTED BY: Brad Klee
Posted 3 years ago

diffs2

Anim. 1 Ambiguity in the Autoglyphs

Start by importing a list of seeds from this list of addresses (sunseted)

txdat=StringSplit[Import["https://0x0.st/-hgJ.txt"],"\n"];
SameQ[ImportByteArray[BlockchainTransactionData[
txdat[[#]],"InputData",BlockchainBase->"Ethereum"],"UnsignedInteger8"],
ImportByteArray[BlockchainTransactionData[
txdat[[#]],"InputData",BlockchainBase->"Ethereum"]]]&/@{1,2,3,4,5}
Out[] = T,F,T,T,T . . . 

Be careful to set "UnsignedInteger8" or the conversion will fail anomalously.

seedInt[tx_]:=Dot[Reverse[ImportByteArray[BlockchainTransactionData[
tx,"InputData",BlockchainBase->"Ethereum"],"UnsignedInteger8"
]][[1;;32]],256^Range[0,31]]
seedIntData=seedInt[txdat[[#]] ]&/@Range[512]

Notice non-homogeneity, we will return to this shortly. Once the seeds are known, the hidden values must also be known:

aVals = ToExpression[StringSplit[
    Import["~/mathfun/AutoGlyphs/hiddenVals.txt"], "\n"]];

In retrospect to the original creators, I am not revealing these $512$ uint values today. The more code is needed to identify schemes and then project different ways for view comparison or empty set verification:

AutoGlyphM[aVal_] := With[{mod = Mod[aVal, 11] + 5},
  Outer[v[x[#2, aVal], y[#1, aVal], mod] &,
   Range[0, SIZE - 1], Range[0, SIZE - 1], 1]]

AbsoluteTiming[  NumGlyphCompute = AutoGlyphM /@ aVals; ]
Out[]=1/2min

AbsoluteTiming[ RawGlyphData = Characters[StringSplit[Import[
        "~/mathfun/AutoGlyphs/RawData/g" <>
         StringPadLeft[ToString[#], 4, "0"] <> ".txt"],
       " "]] & /@ Range[1, 2^9];]
Out[]=3s

divs = Range[#[[1]], #[[2]] - 1] & /@ 
   Partition[{0, 20, 35, 48, 59, 68, 73, 77, 80, 82, 83}, 2, 1];
schemes = Position[divs, Mod[#, 83]][[1, 1]] & /@ aVals;

Lex = Characters[{" X/\\ ", " +-| " , " /\\  ", " \\|-/", " O|- ", 
    " \\\\  ", " #|-+", " OO  ", " #   ", " #O  "}];
RRtoChar =   MapIndexed[(#2[[1]] - 1) -> StringReplace[#1, " " -> "."] &, #] & /@Lex;
RRtoAmb =   MapThread[ Append, {Map[
     MapIndexed[#1 -> #2[[1]] &, Flatten@Position[#, " "] - 1] &, Lex],
    Map[Alternatives @@ 
        Complement[Range[5] - 1, Flatten@Position[#, " "] - 1] -> 0 &,
      Lex]}];

NumToChar[numGlyph_, scheme_] :=  ReplaceAll[numGlyph, RRtoChar[[scheme]]]
ProjAmbiguous[numGlyph_, scheme_] :=  ReplaceAll[numGlyph, RRtoAmb[[scheme]]]

The main objectif of this mission is to print a value indicating empty set. Here we go:

QAdat = SameQ[NumToChar[NumGlyphCompute[[#]], schemes[[#]]],
     RawGlyphData[[#]]] & /@ Range[512];
Flatten[Position[QAdat, False]]
Out[]={}

The proof is complete. The source code has been taken for $512\times$.

While all of this was happening, some of you may have noticed an extra layer in the crypto having to do with byte counting length of unsigned integers, or in computable:

seedData = StringSplit[Import["https://0x0.st/-hg3.txt"], "\n"];
Flatten[With[{lens = StringLength /@ seedData},
  Position[lens, #] & /@ Select[lens, # < 44 &]]]
seedData[[%]]

Out[]= {198, 337}
Out[]={200, 1123581321}

Thus what is next for this analysis? Should we discuss the inherent symmetry of multiplication tables plus or minus a few extra symbols? Of course, the ansers is "Yes", but first, a commercial shout out to the predecessors.

The test functions are written as follows:

TestVSym[glyph_] :=  SameQ[Union[Flatten[glyph - Reverse[glyph]]], {0}]
TestHSym[glyph_] :=  SameQ[Union[Flatten[glyph - Map[Reverse, glyph]]], {0}]
TestDSym[glyph_] :=  SameQ[Union[Flatten[glyph - Transpose[glyph]]], {0}]

Then we must restrict

HSyms = Position[Map[TestHSym, NumGlyphCompute], True][[All, 1]];
VSyms = Position[Map[TestVSym, NumGlyphCompute], True][[All, 1]];
DSyms = Position[Map[TestDSym, NumGlyphCompute], True][[All, 1]];

StrictHSyms = Complement[HSyms, VSyms];
StrictVSyms = Complement[VSyms, HSyms];
StrictDSyms = Complement[DSyms, Join[HSyms, VSyms]];
StrictHVSyms = Intersection[HSyms, VSyms];

symClasses = {StrictHSyms, StrictVSyms, StrictDSyms, StrictHVSyms};

Penultimate finally, here is an interesting proof:

Outer[Length[Intersection[#1, #2]] &, symClasses, symClasses, 1] // MatrixForm
Length /@ {StrictHSyms, StrictVSyms, StrictDSyms, StrictHVSyms}
Total[%]

symtest

which says that all glyphs must have one of four types of symmetry ending with square dihedral in $110$ cases, the most symmetric.

Of course, if there is a penultimate, there must be a final ultimate, "ultima magic", so to speak. Here you go no charges whatsoever:

modClasses = With[{modData = {Mod[#, 2], Mod[#, 3]} & /@ aVals},
   Position[modData, #][[All, 1]] & /@ Union[modData]];
Outer[Length[Intersection[#1, #2]] &, symClasses, modClasses, 
  1] // MatrixForm

classy

Your homework is to interpret this matrix and take the analysis to one level deeper to prove a priori from the source code why it must exist.

Now here is the last issue in this PoC with the metadata located at this address. It must be compared to the state of the art for on-chain computation, meaning that the following code:

function draw(uint id) public view returns (string) . . . 

from etherscan.io, as far as I know, does not exist in a MWK living on the Cardano blockchain.

POSTED BY: Brad Klee
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract