| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175 |
- /= emission /common/schedule
- /= mine /common/pow
- /= * /common/zeke
- /= * /common/zoon
- :: tx-engine: this contains all transaction types and logic related to dumbnet.
- ::
- :: the most notable thing about how this library is written are the types. we use
- :: a namespacing scheme for functions that are primarily about particular types inside
- :: of the namespace for that type, as suggested by Ted in urbit/#6881. that is:
- ::
- :: ++ list
- :: =< form
- :: |%
- :: ++ form |$ [a] $?(~ [i=a t=$])
- :: ++ flop |*(...)
- :: ++ turn |*(...)
- :: ...
- :: --
- ::
- :: we refer to these types as 'B-types'
- ::
- =>
- ~% %dumb-transact ..stark-engine-jet-hook ~
- |%
- +| %misc-types
- ::
- :: size in bits. this is not a blockchain constant, its just an alias
- :: to make it clear what the atom represents and theres not a better spot
- :: for it.
- +$ size @bits
- ::
- :: $blockchain-constants
- ::
- :: type to hold all the blockchain constants. provided for convenience
- :: when using non-default constants.
- +$ blockchain-constants
- $+ blockchain-constants
- $~ :* :: max block size in bits
- max-block-size=`@`8.000.000
- :: actual number of blocks, not 2017 by counting from 0
- blocks-per-epoch=2.016
- :: 14 days measured in seconds, 1.209.600
- target-epoch-duration=^~(:(mul 14 24 60 60))
- :: how long to wait before changing candidate block timestamp
- update-candidate-timestamp-interval=~m2
- :: how far in the future to accept a timestamp on a block
- max-future-timestamp=^~((mul 60 120))
- :: how many blocks in the past to look at to compute median timestamp from
- :: which a new block's timestamp must be after to be considered valid
- min-past-blocks=11
- ::TODO determine appropriate genesis target
- genesis-target-atom=^~((div max-tip5-atom:tip5 (bex 14)))
- ::TODO determine a real max-target-atom. BTC uses 32 leading zeroes
- max-target-atom=max-tip5-atom:tip5
- :: whether or not to check the pow of blocks
- check-pow-flag=&
- :: minimum range of coinbase timelock
- coinbase-timelock-min=100
- :: pow puzzle length
- pow-len=pow-len
- :: how many ways the coinbase may be split
- max-coinbase-split=2
- :: first month block height. enforces lock on first month coins.
- first-month-coinbase-min=4.383
- ==
- $: max-block-size=size
- blocks-per-epoch=@
- target-epoch-duration=@
- update-candidate-timestamp-interval=@dr
- max-future-timestamp=@
- min-past-blocks=@
- genesis-target-atom=@
- max-target-atom=@
- check-pow-flag=?
- coinbase-timelock-min=@
- pow-len=@
- max-coinbase-split=@
- first-month-coinbase-min=@
- ==
- --
- ::
- :: tx-engine
- ::
- :: contains the tx engine. the default sample for the door is mainnet constants,
- :: pass something else in if you need them (and make sure to use the same door
- :: for all calls into this library).
- ~% %tx-engine +> ~
- |_ blockchain-constants
- +| %constants
- :: one quarter epoch duration - used in target adjustment calculation
- ++ quarter-ted ^~((div target-epoch-duration 4))
- :: 4x epoch duration - used in target adjustment calculation
- ++ quadruple-ted ^~((mul target-epoch-duration 4))
- ++ genesis-target ^~((chunk:bignum genesis-target-atom)) ::TODO set this
- ++ max-target ^~((chunk:bn max-target-atom))
- +| %simple-tx-engine-types
- +$ block-commitment hash
- +$ tx-id hash
- +$ coins @ud :: the smallest unit, i.e. atoms.
- +$ page-number @ud
- ++ bn bignum
- +$ page-summary :: used for status updates
- $: digest=block-id
- timestamp=@
- epoch-counter=@
- target=bignum:bn
- accumulated-work=bignum:bn
- height=page-number
- parent=block-id
- ==
- ::
- +| %complex-tx-engine-types
- ++ btc-hash
- =< form
- |%
- ++ form
- $+ btc-hash
- [@ux @ux @ux @ux @ux @ux @ux @ux]
- ++ hashable |=(=form leaf+form)
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- ++ block-id
- =< form
- |%
- ++ form hash
- ++ to-list to-list:hash
- ++ based based:hash
- ++ max-size max-size:hash
- --
- :: $hash: output of tip:zoon arm
- ++ hash
- =< form
- |%
- ++ form
- $+ noun-digest
- [@ux @ux @ux @ux @ux]
- ::
- ++ to-b58 |=(has=form `cord`(crip (en-base58 (digest-to-atom:tip5 has))))
- ++ from-b58 |=(=cord `form`(atom-to-digest:tip5 (de-base58 (trip cord))))
- ::
- :: +max-size: max size of hash in bits, obtained by running:
- :: (compute-size-jam [(dec p) (sub p 2) (sub p 3) (sub p 4) (sub p 5)])
- ++ max-size
- ^- size
- `size``@`403
- ::
- :: +validate: checks if elements of hash are in base field.
- ++ based
- |= has=form
- ^- ?
- =+ [a=@ b=@ c=@ d=@ e=@]=has
- ?& (^based a)
- (^based b)
- (^based c)
- (^based d)
- (^based e)
- ==
- ::
- ++ to-list
- |= bid=form
- ^- (list @)
- =+ [a=@ b=@ c=@ d=@ e=@]=bid
- [a b c d e ~]
- --
- ::
- ++ proof
- =< form
- |%
- +$ form ^proof
- ::
- :: +max-size: max size of proof in bits. We upper bound it to
- :: 125kb or 125000 bytes or 1000000 bits
- ++ max-size
- ^- size
- `size``@`1.000.000
- ::
- ++ hash |=(=form (hash-proof form))
- --
- ::
- ++ schnorr-pubkey
- =< form
- |%
- +$ form a-pt:curve:cheetah
- ::
- ++ based
- |= =form
- ^- ?
- (a-pt-based:curve:cheetah form)
- ::
- ++ validate
- |= =form
- (in-g:affine:curve:cheetah form)
- ::
- ++ to-b58 |=(sop=form `cord`(a-pt-to-base58:cheetah sop))
- ++ from-b58 |=(=cord `form`(base58-to-a-pt:cheetah cord))
- ++ to-lock |=(sop=form (new:lock sop))
- ++ hash |=(=form (hash-hashable:tip5 leaf+form))
- --
- ++ schnorr-seckey
- =< form
- |%
- +$ form sk:belt-schnorr:cheetah
- ::
- ++ from-atom
- |= sk=@ux
- ^- form
- (atom-to-t8:belt-schnorr:cheetah sk)
- ::
- ++ to-atom
- |= sk=form
- (t8-to-atom:belt-schnorr:cheetah sk)
- --
- ++ schnorr-signature
- =< form
- |%
- +$ form
- [chal=chal:belt-schnorr:cheetah sig=sig:belt-schnorr:cheetah]
- ::
- ++ based
- |= =form
- ?& (based:belt-schnorr:cheetah chal.form)
- (based:belt-schnorr:cheetah sig.form)
- ==
- ::
- ++ hashable |=(=form leaf+form)
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- :: $signature: multisigs, with a single sig as a degenerate case
- ++ signature
- =< form
- |%
- +$ form (z-map schnorr-pubkey schnorr-signature)
- ::
- ++ based
- |= =form
- ^- ?
- %+ levy ~(tap z-by form)
- |= [pubkey=schnorr-pubkey sig=schnorr-signature]
- ?& (based:schnorr-pubkey pubkey)
- (based:schnorr-signature sig)
- ==
- ++ hashable
- |= =form
- ^- hashable:tip5
- ?~ form leaf+form
- :+ [hash+(hash:schnorr-pubkey p.n.form) (hashable:schnorr-signature q.n.form)]
- $(form l.form)
- $(form r.form)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $source: commitment to sources of an note
- ::
- :: for an ordinary note, this is a commitment to the notes that spend into a
- :: given note. for a coinbase, this is the hash of the previous block (this avoids
- :: a hash loop in airwalk)
- ::
- :: so you should be able to walk backwards through the sources of any transaction,
- :: and the notes that spent into that, and the notes that spent into those, etc,
- :: until you reach coinbase(s) at the end of that walk.
- ++ source
- =< form
- |%
- +$ form [p=^hash is-coinbase=?]
- ::
- ++ from-b58
- |= [h=@t c=?]
- %* . *form
- p (from-b58:^hash h)
- is-coinbase c
- ==
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- :- hash+p.form
- leaf+is-coinbase.form
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $nname: unique note identifier
- ::
- :: first hash is a commitment to the note's .lock and whether or
- :: not it has a timelock.
- ::
- :: second hash is a commitment to the note's source and actual
- :: timelock
- ::
- :: there are also stubs for pacts, which are currently unimplemented.
- :: but they are programs that must return %& in order for the note
- :: to be spendable, and are included in the name of the note. right
- :: now, pacts are ~ and always return %&.
- ::
- ::TODO for dumbnet, this will be [hash hash ~] but eventually we want (list hash)
- ::which should be thought of as something like a top level domain, subdomain, etc.
- ++ nname
- =< form
- ~% %nname ..nname ~
- |%
- +$ form [^hash ^hash ~]
- ::
- ++ new
- =< default
- |%
- ++ default
- |= [owners=lock =source =timelock]
- ^- form
- =/ first-name
- %- hash-hashable:tip5
- :* leaf+& :: outcome of first pact
- leaf+!=(~ timelock) :: does it have a timelock?
- hash+(hash:lock owners) :: owners of note
- leaf+~ :: first pact
- ==
- =/ last-name
- %- hash-hashable:tip5
- :* leaf+& :: outcome of second pact
- (hashable:^source source) :: source of note
- hash+(hash:^timelock timelock) :: timelock of note
- leaf+~ :: second pact
- ==
- [first-name last-name ~]
- ::
- ++ simple
- |= [owners=lock =source]
- ^- form
- (new owners source *timelock)
- --
- ::
- ++ from-b58
- |= [first=@t last=@t]
- ^- form
- :~ (from-b58:^hash first)
- (from-b58:^hash last)
- ==
- ::
- ++ to-b58
- |= nom=form
- ^- [first=@t last=@t]
- :- (to-b58:^hash -.nom)
- (to-b58:^hash +<.nom)
- ::
- ++ based
- |= =form
- ?& (based:^hash -.form)
- (based:^hash -.+.form)
- ==
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- [[%hash -.form] [%hash +<.form] [%leaf +>.form]]
- ::
- ++ hash
- ~/ %hash
- |= =form
- (hash-hashable:tip5 (hashable form))
- -- ::+name
- ::
- :: $page: a block
- ::
- :: .digest: block hash, hash of +.page
- :: .pow: stark seeded by hash of +>.page
- :: .parent: .digest of parent block
- :: .tx-ids: ids of txs included in block
- :: .coinbase: $coinbase-split
- :: .timestamp:
- :: time from (arbitrary time) in seconds. not exact.
- :: practically, it will never exceed the goldilocks prime.
- :: .epoch-counter: how many blocks in current epoch (0 to 2015)
- :: .target: target for current epoch
- :: .accumulated-work: sum of work over the chain up to this point
- :: .height: page number of block
- :: .msg: optional message as a (list belt)
- ::
- :: if you're wondering where the nonce is, its in the %puzzle
- :: of a $proof.
- ::
- :: fields for the commitment are ordered from most frequently updated
- :: to least frequently updated for merkleizing efficiency - except for
- :: .parent, in order to allow for PoW-chain proofs to be as small as
- :: possible.
- ++ page
- =< form
- |%
- +$ form
- $+ page
- $: digest=block-id
- :: everything below this is what is hashed for the digest: +.page
- pow=$+(pow (unit proof))
- :: everything below this is what is hashed for the block commitment: +>.page
- parent=block-id ::TODO sam's comment on why this is here
- tx-ids=(z-set tx-id)
- coinbase=coinbase-split
- timestamp=@
- epoch-counter=@ud
- target=bignum:bn
- accumulated-work=bignum:bn
- height=page-number
- msg=page-msg
- ==
- ::
- :: +new: builds a minimally populated page given a parent page and key
- ::
- :: when a $page is built with +new, it is the minimal amount of state
- :: needed to call +block-commitment on it and then pass that commit
- :: to the miner to start mining on an empty block.
- ::
- :: genesis block should be built with new-genesis
- ::
- :: while we store target and accumulated-work as bignums, we
- :: do not yet employ bignum arithmetic
- ::
- :: We assume that par is a valid block that was obtained from
- :: the node's blockmap. The other arguments in the sample are
- :: also validated in the context in +new is called, so we skip
- :: validation. Notably, shares is obtained from the mining state,
- :: where it was validated in +set-shares.
- ++ new-candidate
- |= [par=form now=@da target-bn=bignum:bn shares=(z-map lock @)]
- ^- form
- =/ accumulated-work=bignum:bn
- %- chunk:bn
- (add (merge:bn (compute-work target-bn)) (merge:bn accumulated-work.par))
- =/ epoch-counter=@
- ?: =(+(epoch-counter.par) blocks-per-epoch) 0
- +(epoch-counter.par)
- =/ height=@ +(height.par)
- %* . *form
- ::minimum information needed to generate a valid block commitment, so
- ::that a miner can start mining on an empty block.
- height height
- parent digest.par
- timestamp (time-in-secs now)
- epoch-counter epoch-counter
- target target-bn
- accumulated-work accumulated-work
- coinbase %+ new:coinbase-split
- (emission-calc:coinbase height)
- shares
- ==
- ::
- :: +new-genesis: builds a minimally populated $page suitable to mine as genesis block.
- ::
- ++ new-genesis
- |= [tem=genesis-template timestamp=@da]
- ^- form
- :: explicitly writing out the bunts is unnecessary, but we want to make it clear
- :: that each of these choices was deliberate rather than unfinished
- %* . *form
- pow *(unit proof) :: pow is uninitialized because it needs to be mined
- tx-ids *(z-set tx-id)
- timestamp (time-in-secs timestamp)
- epoch-counter *@
- target genesis-target
- accumulated-work (compute-work genesis-target)
- coinbase *(z-map lock coins) :: ensure coinbase is unspendable
- height *page-number
- parent (hash:btc-hash btc-hash.tem)
- msg message.tem
- ==
- ::
- ::
- :: +block-commitment: hash commitment of block contents for miner
- ::
- :: this hashes everything after the .pow
- ++ hashable-block-commitment
- |= =form
- ^- hashable:tip5
- |^
- :* hash+parent.form
- hash+(hash-hashable:tip5 (hashable-tx-ids tx-ids.form))
- hash+(hash:coinbase-split coinbase.form)
- leaf+timestamp.form
- leaf+epoch-counter.form
- leaf+target.form
- leaf+accumulated-work.form
- leaf+height.form
- leaf+msg.form
- ==
- ::
- ++ hashable-tx-ids
- |= tx-ids=(z-set tx-id)
- ^- hashable:tip5
- ?~ tx-ids leaf+tx-ids
- :+ hash+n.tx-ids
- $(tx-ids l.tx-ids)
- $(tx-ids r.tx-ids)
- --
- ::
- ++ block-commitment
- |= =form
- (hash-hashable:tip5 (hashable-block-commitment form))
- ::
- ++ check-digest
- |= pag=form
- ^- ?
- ?& (based:block-id digest.pag)
- =(digest.pag (compute-digest pag))
- ==
- ::
- :: Hash pow with hash-proof and hash the rest of the page.
- ++ compute-digest
- |= pag=form
- ^- block-id
- %- hash-hashable:tip5
- :- ?~ pow.pag leaf+~
- [leaf+~ hash+(hash-proof u.pow.pag)]
- (hashable-block-commitment pag)
- ::
- :: +time-in-secs: returns @da in seconds.
- ++ time-in-secs
- |= now=@da
- ^- @
- =/ tar=tarp (yell now)
- ;: add
- (mul d.tar day:yo) :: seconds in a day
- (mul h.tar hor:yo) :: seconds in an hour
- (mul m.tar mit:yo) :: seconds in a minute
- s.tar :: seconds in a second
- ==
- ::
- :: +compute-size:
- ::
- :: this is equal to the size of the jammed $page plus the size of all the
- :: transactions (which are not inlined in the block, so must also be passed
- :: in). we pass in raw-txs instead of txs because this is utilized when building
- :: candidate blocks, and so the txs will not be available in the $consensus-state.
- ++ compute-size
- |= [pag=form raw-txs=(z-map tx-id raw-tx)]
- ^- size
- ;: add
- :: max size of digest in bits, we need to check against upper bound because
- :: a digest cannot be calculated without pow
- max-size:block-id
- :: max size of proof is 90 kb or 90000 bytes or 720000 bits
- max-size:proof
- :: size of page in number of bits. note that we do not include the digest
- :: or powork.
- (compute-size-jam `*`+>.pag)
- ::
- %+ roll
- ~(tap z-in tx-ids.pag)
- |= [id=tx-id sum-sizes=size]
- %+ add sum-sizes
- (compute-size:raw-tx (~(got z-by raw-txs) id))
- ==
- ::
- ++ to-local-page
- |= pag=form
- ^- local-page
- pag(pow (bind pow.pag |=(p=proof (jam p))))
- ::
- :: +compute-work: how much heaviness a block contribute to .accumulated-work
- ::
- :: see GetBlockProof in https://github.com/bitcoin/bitcoin/blob/master/src/chain.cpp
- :: last changed in commit 306ccd4927a2efe325c8d84be1bdb79edeb29b04 for the source
- :: of this formula.
- ::
- :: while we store target and work as bignums, we do not yet utilize bignum arithmetic.
- ++ compute-work
- |= target-bn=bignum:bn
- ^- bignum:bn
- =/ target-atom=@ (merge:bn target-bn)
- (chunk:bn (div max-target-atom +(target-atom)))
- ::
- ++ to-page-summary
- |= pag=form
- ^- page-summary
- :* digest.pag
- timestamp.pag
- epoch-counter.pag
- target.pag
- accumulated-work.pag
- height.pag
- parent.pag
- ==
- ::
- :: +compare-heaviness: %.y if first page is heavier, %.n otherwise
- ::
- :: second arg is $local-page since that is always how this is done right now.
- ++ compare-heaviness
- |= [pag1=form pag2=local-page]
- ^- ?
- %+ gth (merge:bn accumulated-work.pag1)
- (merge:bn accumulated-work.pag2)
- --
- ::
- :: A locally-stored page. The only difference from +page is that pow is jammed
- :: to save space. Must be converted into a +page (ie cue the pow) for hashing.
- ++ local-page
- =< form
- |%
- +$ form
- $+ local-page
- $: digest=block-id
- pow=$+(pow (unit @))
- parent=block-id
- tx-ids=(z-set tx-id)
- coinbase=coinbase-split
- timestamp=@
- epoch-counter=@ud
- target=bignum:bn
- accumulated-work=bignum:bn
- height=page-number
- msg=page-msg
- ==
- ::
- ++ to-page
- |= lp=form
- ^- page
- lp(pow (biff pow.lp |=(j=@ ((soft proof) (cue j)))))
- --
- ::
- :: +page-msg: (list belt) that enforces that each elt is a belt
- ++ page-msg
- =< form
- |%
- +$ form (list belt)
- ::
- ++ new |=(msg=cord (form (rip-correct 5 msg)))
- ++ validate
- |= tap=(list belt)
- ^- ?
- (levy tap |=(t=@ (based t)))
- ::
- ++ hash
- |= =form
- ^- ^hash
- (hash-hashable:tip5 leaf+form)
- --
- ::
- :: +genesis-seal: information to identify the correct genesis block
- ::
- :: before nockchain is launched, a bitcoin block height and message
- :: hash will be publicly released. the height is the height at which
- :: nockchain will be launched. the "correct" genesis block will
- :: be identified by matching the message hash with the hash of the
- :: message in the genesis block, and then confirming that the parent
- :: of the genesis block is a hash built from the message, the height,
- :: and the hash of the bitcoin block at that height.
- ::
- :: the height and message hash are known as the "genesis seal".
- ::
- ++ genesis-seal
- =< form
- |%
- +$ form
- %- unit
- $: block-height=belt
- msg-hash=hash
- ==
- ::
- ++ new
- |= [height=page-number msg-hash=@t]
- ^- form
- `[height (from-b58:hash msg-hash)]
- --
- ::
- :: $genesis-template:
- ::
- :: supplies the block hash and height of the Bitcoin block which must be
- :: used for the genesis block. note that the hash is a SHA256, while we
- :: want a 5-tuple $noun-digest. we call +new in this core with the raw
- :: atom representing the SHA256 hash, which then converts it into a 5-tuple.
- ::
- ++ genesis-template
- =< form
- |%
- +$ form
- $: =btc-hash
- block-height=@ :: interpreted as a belt
- message=page-msg
- ==
- ::
- ++ new
- |= [=btc-hash block-height=@ message=cord]
- ^- form
- =/ split-msg (new:page-msg message)
- [btc-hash block-height split-msg]
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- [leaf+btc-hash.form leaf+block-height.form hash+(hash:page-msg message.form)]
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- ++ inputs
- =< form
- ~% %inputs ..inputs ~
- |%
- +$ form (z-map nname input)
- ::
- ++ new
- =< default
- |%
- ++ default
- |= =input
- ^- form
- (~(put z-by *form) [name.note.input input])
- ::
- ++ multi
- |= ips=(list input)
- ^- form
- %- ~(gas z-by *form)
- %+ turn ips
- |= inp=input
- [name.note.inp inp]
- --
- ::
- ++ names
- |= ips=form
- ^- (z-set nname)
- ~(key z-by ips)
- ::
- ++ roll-fees
- |= ips=form
- ^- coins
- %+ roll ~(val z-by ips)
- |= [inp=input fees=coins]
- (add fee.spend.inp fees)
- ::
- ++ roll-timelocks
- |= ips=form
- ^- timelock-range
- %+ roll ~(val z-by ips)
- |= [ip=input range=timelock-range]
- %+ merge:timelock-range
- range
- (fix-absolute:timelock timelock.note.ip origin-page.note.ip)
- ::
- :: +validate: calls validate:input on each input, and checks key/value
- ++ validate
- ~/ %validate
- |= ips=form
- ^- ?
- ?: =(ips *form) %.n :: tx with no inputs are not allowed.
- %+ levy ~(tap z-by ips)
- |= [name=nname inp=input]
- ?& (validate:input inp)
- =(name name.note.inp)
- ==
- ::
- ++ based
- ~/ %based
- |= ips=form
- ^- ?
- ?: =(ips *form)
- %&
- %+ levy ~(tap z-by ips)
- |= [name=nname inp=input]
- ?& (based:input inp)
- (based:nname name)
- ==
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- ?~ form leaf+form
- :+ [(hashable:nname p.n.form) (hashable:input q.n.form)]
- $(form l.form)
- $(form r.form)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- ++ outputs
- =< form
- ~% %outputs ..outputs ~
- |%
- +$ form (z-map lock output) :: lock is the recipient
- ::
- ++ new
- ~/ %new
- |= [ips=inputs new-page-number=page-number]
- ^- form
- ?: =(ips *inputs) !! :: zero utxo tx not allowed
- =| children=form
- =/ inputs=(list input) ~(val z-by ips)
- |-
- ?~ inputs
- (birth-children children new-page-number)
- ?. (validate:input i.inputs)
- ~& >>>
- :* %failed-spend-validate
- "note name "
- name.note.i.inputs
- " signature "
- signature.spend.i.inputs
- ==
- !!
- =/ seed-list=(list seed) ~(tap z-in seeds.spend.i.inputs)
- |-
- ?~ seed-list
- ^$(inputs t.inputs)
- =. children (add-seed children i.seed-list)
- $(seed-list t.seed-list)
- ::
- :: +validate: calls validate:output on each output, and checks key/value
- ++ validate
- ~/ %validate
- |= ops=form
- ^- ?
- %+ levy ~(tap z-by ops)
- |= [loc=lock out=output]
- ?& (validate:output out)
- =(loc lock.note.out)
- ==
- ::
- :: +add-seed: adds seed to $outputs of a tx
- ::
- :: this iterates over the children and checks to see if any of them
- :: have the same $lock as the seed. if so, add the seed to that child.
- :: otherwise, create a new child that contains the seed.
- ++ add-seed
- |= [children=form sed=seed]
- ^+ children
- ?: (~(has z-by children) recipient.sed)
- =/ child=output (~(got z-by children) recipient.sed)
- ?: (~(has z-in seeds.child) sed)
- ~& >>> "can't add same seed to an output more than once"
- !!
- =. seeds.child (~(put z-in seeds.child) sed)
- (~(put z-by children) recipient.sed child)
- =/ child=output
- %* . *output
- seeds (~(put z-in *seeds) sed)
- ==
- (~(put z-by children) recipient.sed child)
- ::
- ++ birth-children
- |= [children=form new-page-number=page-number]
- ^+ children
- |^
- =. children
- %- ~(run z-by children)
- |= child=output
- =. origin-page.note.child new-page-number
- :: to avoid a hash-loop, we hash the tails of the seeds
- =. source.note.child (compute-source:output child)
- =. child
- %+ roll ~(tap z-in seeds.child)
- |= [=seed chi=_child]
- =? timelock.note.chi !=(~ timelock-intent.seed)
- (reconcile timelock.note.child timelock-intent.seed)
- =. assets.note.chi (add gift.seed assets.note.chi)
- =. lock.note.chi recipient.seed
- chi
- ::
- =. name.note.child
- %- new:nname
- :* lock.note.child
- source.note.child
- timelock.note.child
- ==
- child
- children
- ::
- ++ reconcile
- |= [a=timelock b=timelock-intent]
- ^- timelock
- ?~ b a
- =/ b-timelock=timelock (convert-from-intent:timelock b)
- ?~ a b-timelock
- ?:(=(a b-timelock) a !!)
- -- ::+birth-children
- -- ::+outputs
- ::
- :: +raw-tx: a tx as found in the mempool, i.e. the wire format of a tx.
- ::
- :: in order for a raw-tx to grow up to become a tx, it needs to be included in
- :: a block. some of the data of a tx cannot be known until we know which block
- :: it is in. a raw-tx is all the data we can know about a transaction without
- :: knowing which block it is in. when a miner reads a raw-tx from the mempool,
- :: it should first run validate:raw-tx on it to check that the inputs are signed.
- :: then the miner can begin deciding how
- ::TODO we might want an unsigned version of this as well
- ++ raw-tx
- =< form
- ~% %raw-tx ..raw-tx ~
- |%
- +$ form
- $: id=tx-id :: hash of +.raw-tx
- =inputs
- :: the "union" of the ranges of valid page-numbers
- :: in which all inputs of the tx are able to spend,
- :: as enforced by their timelocks
- =timelock-range
- :: the sum of all fees paid by all inputs
- total-fees=coins
- ==
- ++ new
- =< default
- ~% %new ..new ~
- |%
- ++ default
- ~/ %default
- |= ips=inputs
- ^- form
- =/ raw-tx=form
- %* . *form
- inputs ips
- total-fees (roll-fees:inputs ips)
- timelock-range (roll-timelocks:inputs ips)
- ==
- =. raw-tx raw-tx(id (compute-id raw-tx))
- ?> (validate raw-tx)
- raw-tx
- :: +simple-from-note: send all assets from note to recipient
- ++ simple-from-note
- =< default
- |%
- ++ default
- |= [recipient=lock not=nnote sk=schnorr-seckey]
- ^- form
- %- new
- %- new:inputs
- %- simple-from-note:new:input
- :+ recipient
- not
- sk
- :: +with-refund: send all assets from note to recipient, remainder to owner
- ++ with-refund
- |= [recipient=lock gift=coins fees=coins not=nnote sk=schnorr-seckey]
- ^- form
- %- new
- %- new:inputs
- %- with-refund:simple-from-note:new:input
- :* recipient
- gift
- fees
- not
- sk
- ==
- --
- --
- ::
- ++ compute-id
- |= raw=form
- ^- tx-id
- %- hash-hashable:tip5
- :+ (hashable:inputs inputs.raw)
- (hashable:timelock-range timelock-range.raw)
- leaf+total-fees.raw
- ::
- ++ based
- ~/ %based
- |= raw=form
- ^- ?
- ?& (based:hash id.raw)
- (based:inputs inputs.raw)
- (based:timelock-range timelock-range.raw)
- (^based total-fees.raw)
- ==
- ::
- ++ validate
- ~/ %validate
- |= raw=form
- ^- ?
- =/ check-inputs (validate:inputs inputs.raw)
- =/ check-fees =(total-fees.raw (roll-fees:inputs inputs.raw))
- =/ check-timelock =(timelock-range.raw (roll-timelocks:inputs inputs.raw))
- =/ check-field (based:hash id.raw)
- =/ check-id =(id.raw (compute-id raw))
- :: %- %- slog
- :: :~ leaf+"validate-raw-tx"
- :: leaf+"inputs: {<check-inputs>}"
- :: leaf+"fees: {<check-fees>}"
- :: leaf+"timelock: {<check-timelock>}"
- :: leaf+"field: {<check-field>}"
- :: leaf+"id: {<check-id>}"
- :: ==
- ?& check-inputs
- check-fees
- check-timelock
- check-field
- check-id
- ==
- ::
- ++ inputs-names
- |= raw=form
- ^- (z-set nname)
- (names:inputs inputs.raw)
- ::
- :: +compute-size: returns size in number of bits
- ++ compute-size
- |= raw=form
- ^- size
- (compute-size-jam `*`raw)
- --
- ::
- :: $tx: once a raw-tx is being included in a block, it becomes a tx
- ++ tx
- =< form
- ~% %tx ..tx ~
- |%
- +$ form
- $: [raw-tx] :: this makes it so the head of a tx is a raw-tx
- total-size=size :: the size of the raw-tx
- =outputs
- ==
- ::
- ++ new
- ~/ %new
- |= [raw=raw-tx new-page-number=page-number]
- ^- form
- =/ ops=outputs
- (new:outputs inputs.raw new-page-number)
- %* . *form
- id id.raw
- inputs inputs.raw
- timelock-range timelock-range.raw
- total-fees total-fees.raw
- outputs ops
- total-size (compute-size:raw-tx raw)
- ==
- ::
- ++ validate
- ~/ %validate
- |= [tx=form new-page-number=page-number]
- ^- ?
- ?& (validate:raw-tx -.tx) :: inputs, total-fees, timelock-range, id
- (validate:outputs outputs.tx)
- =(total-size.tx (compute-size tx))
- ==
- ::
- ++ compute-size
- |= tx=form
- ^- size
- (compute-size:raw-tx -.tx)
- -- ::+tx
- ::
- :: $timelock-intent: enforces $timelocks in output notes from $seeds
- ::
- :: the difference between $timelock and $timelock-intent is that $timelock-intent
- :: permits the values ~ and [~ ~ ~] while $timelock does not permit [~ ~ ~].
- :: the reason for this is that a non-null timelock intent forces the output
- :: note to have this timelock. so a ~ means it does not enforce any timelock
- :: restriction on the output note, while [~ ~ ~] means that the output note
- :: must have a timelock of ~.
- ++ timelock-intent
- =< form
- ~% %timelock-intent ..timelock-intent ~
- |%
- +$ form
- %- unit :: a value of ~ indicates "no intent"
- $: absolute=timelock-range :: a range of absolute page-numbers
- ::
- :: a range of relative diffs between the page-number of the note
- :: and the range of absolute page-numbers in which the note may spend
- relative=timelock-range
- ==
- ::
- ++ based
- ~/ %based
- |= =form
- ?~ form
- %&
- ?& (based:timelock-range absolute.u.form)
- (based:timelock-range relative.u.form)
- ==
- ::
- ++ hashable
- ~/ %hashable
- |= =form
- ^- hashable:tip5
- ?~ form leaf+~
- :+ leaf+~
- (hashable:timelock-range absolute.u.form)
- (hashable:timelock-range relative.u.form)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $timelock: an absolute and relative range of page numbers this note may be spent
- ++ timelock
- =< form
- ~% %timelock ..timelock ~
- |%
- :: A timelock, in terms of values, is a $timelock-intent that does not permit [~ ~ ~]
- +$ form $|(timelock-intent |=(timelock-intent !=(+< [~ ~ ~])))
- ::
- ++ convert-from-intent
- |= int=timelock-intent
- ^- form
- ?: =(int [~ ~ ~]) *form
- int
- ::
- :: +fix-absolute: produce absolute timelock from relative timelock and page number
- ++ fix-absolute
- |= [til=form page-num=page-number]
- ^- timelock-range
- ?~ til *timelock-range
- =/ make-absolute |=(relative=page-number (add relative page-num))
- =/ absolutification=timelock-range
- ?: =(*timelock-range relative.u.til) *timelock-range
- =/ min=(unit page-number)
- (bind min.relative.u.til make-absolute)
- =/ max=(unit page-number)
- (bind max.relative.u.til make-absolute)
- (new:timelock-range [min max])
- (merge:timelock-range absolutification absolute.u.til)
- ::
- ++ hash
- ~/ %hash
- |= =form
- (hash-hashable:tip5 (hashable:timelock-intent form))
- --
- ::
- :: $timelock-range: unit range of pages
- ::
- :: the union of all valid ranges in which all inputs of a tx may spend
- :: given their timelocks. for the dumbnet, we only permit at most one utxo
- :: with a non-null timelock-range per transaction.
- ++ timelock-range
- =< form
- |%
- +$ form [min=(unit page-number) max=(unit page-number)]
- ::
- :: +new: constructor for $timelock-range
- ++ new
- |= [min=(unit page-number) max=(unit page-number)]
- ^- form
- [min max]
- ::
- :: +check: check that a $page-number is in a $timelock-range
- ++ check
- |= [tir=form new-page-number=page-number]
- ^- ?
- ?: =(tir *form) %.y
- =/ min-ok=?
- ?~ min.tir %.y
- (gte new-page-number u.min.tir)
- =/ max-ok=?
- ?~ max.tir %.y
- (lte new-page-number u.max.tir)
- &(min-ok max-ok)
- ::
- ++ merge
- |= [a=form b=form]
- ^- form
- ?: =(a *form)
- ?: =(b *form)
- *form
- b
- ?: =(b *form)
- a
- =/ min=(unit page-number)
- ?~ min.a
- ?~ min.b
- *(unit page-number)
- min.b
- ?~ min.b
- min.a
- `(max u.min.a u.min.b)
- =/ max=(unit page-number)
- ?~ max.a
- ?~ max.b
- *(unit page-number)
- max.b
- ?~ max.b
- max.a
- `(^min u.max.a u.max.b)
- (new [min max])
- ::
- ++ based
- |= =form
- ^- ?
- ?& ?~(min.form %& (^based u.min.form))
- ?~(max.form %& (^based u.max.form))
- ==
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- :- ?~(min.form %leaf^~ [%leaf^~ leaf+u.min.form])
- ?~(max.form %leaf^~ [%leaf^~ leaf+u.max.form])
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $lock: m-of-n signatures needed to spend a note
- ::
- :: m (the number of sigs needed) and n (the number of possible signers)
- :: must both fit in an 8-bit number, and not be 0. so 1 <= n,m <= 255. While
- :: a lock may only be "unlocked" if m =<n, we do permit constructing m>n
- :: with an issued warning, since this may happen when constructing a
- :: transaction piece-by-piece.
- ::
- :: TODO: disambiguate intermediate locks and final locks for validation.
- ++ lock
- =< form
- ~% %lock ..lock ~
- |%
- +$ form
- $~ [m=1 pubkeys=*(z-set schnorr-pubkey)]
- [m=@udD pubkeys=(z-set schnorr-pubkey)]
- ::
- ++ validate
- |= =form
- ^- ?
- ?& (validate-intermediate form)
- (lte m.form ~(wyt z-in pubkeys.form))
- ==
- ::
- ++ validate-intermediate
- |= form
- ^- ?
- =/ num-keys=@ ~(wyt z-in pubkeys)
- ::
- :: validate:schnorr-pubkey is computationally intensive,
- :: do not call this in performance sensistive code
- =/ check-a-pt=?
- %+ levy ~(tap z-in pubkeys)
- validate:schnorr-pubkey
- :: we do not check m <= num-keys here because we allow constructing a tx
- :: piece by piece
- ?& check-a-pt
- (lte m 255)
- !=(m 0)
- (lte num-keys 255)
- !=(num-keys 0)
- ==
- ::
- ++ check
- |= =form
- ^- ^form
- ?. (validate-intermediate form)
- !!
- form
- ::
- :: +based: checks if all components of lock struct are in based field. called
- :: on the block receiver side. we skip validating that the pubkeys are valid
- :: curve points because it is costly. in cases where checking validity is just
- :: as fast as checking for field membership, we check for validity.
- ++ based
- |= =form
- ?. (lte m.form 255)
- %|
- ?. (lte ~(wyt z-in pubkeys.form) 255)
- %|
- %+ levy ~(tap z-in pubkeys.form)
- |= pt=schnorr-pubkey
- ?& (a-pt-based:curve:cheetah pt)
- :: this extra validity check costs nothing, so we throw it in
- :: even though both %.n and %.y are belts.
- =(%.n inf.pt)
- ==
- ::
- ++ new
- =< default
- |%
- ++ default
- |= key=schnorr-pubkey
- %- check
- :* m=1
- pubkeys=(~(put z-in *(z-set schnorr-pubkey)) key)
- ==
- ::
- :: +m-of-n: m signers required of n=#keys.
- ++ m-of-n
- |= [m=@ud keys=(z-set schnorr-pubkey)]
- %- check
- =/ n=@ ~(wyt z-in keys)
- ?> ?& (lte m 255)
- (lte n 255)
- !=(m 0) :: 0-sigs not allowed
- !=(n 0) :: need at least 1 signer
- ==
- ~? >>> (lth n m)
- """
- warning: lock requires more signatures {(scow %ud m)} than there
- are in .pubkeys: {(scow %ud n)}
- """
- [m=m pubkeys=keys]
- --
- ::
- :: +join: union of several $locks
- ++ join
- |= [m=@udD locks=(list form)]
- ^- form
- %- check
- =/ new-keys=(z-set schnorr-pubkey)
- %+ roll locks
- |= [loc=form keys=(z-set schnorr-pubkey)]
- (~(uni z-in keys) pubkeys.loc)
- (m-of-n:new m new-keys)
- ::
- ++ set-m
- |= [lock=form new-m=@ud]
- ^+ lock
- %- check
- =/ n=@ ~(wyt z-in pubkeys.lock)
- ?> ?& (lte new-m 255)
- !=(new-m 0)
- ==
- ~? >>> (lth n new-m)
- """
- warning: lock requires more signatures {(scow %ud new-m)} than there
- are in .pubkeys: {(scow %ud n)}
- """
- lock(m new-m)
- ::
- ++ signers
- |%
- ++ add
- =< default
- |%
- ++ default
- |= [lock=form new-key=schnorr-pubkey]
- ^+ lock
- %- check
- ?: (~(has z-in pubkeys.lock) new-key)
- ~& >>> "signer {<new-key>} already exists in lock"
- lock
- =/ new-keys=(z-set schnorr-pubkey)
- (~(put z-in pubkeys.lock) new-key)
- ?> (lte ~(wyt z-in new-keys) 255)
- %_ lock
- pubkeys new-keys
- ==
- ++ multi
- |= [lock=form new-keys=(z-set schnorr-pubkey)]
- ^+ lock
- %- check
- %- ~(rep z-in new-keys)
- |= [new-key=schnorr-pubkey new-lock=_lock]
- (default new-lock new-key)
- --
- ::
- ++ remove
- =< default
- |%
- ++ default
- |= [lock=form no-key=schnorr-pubkey]
- ^+ lock
- %- check
- ?. (~(has z-in pubkeys.lock) no-key)
- ~& >>> "key {<no-key>} does not exist in lock"
- lock
- =/ new-keys=(z-set schnorr-pubkey)
- (~(del z-in pubkeys.lock) no-key)
- =/ num-keys=@ ~(wyt z-in new-keys)
- ~? >>> (lth num-keys m.lock)
- """
- warning: lock requires more signatures {(scow %ud m.lock)} than there
- are in .pubkeys: {(scow %ud num-keys)}
- """
- lock(pubkeys new-keys)
- ::
- ++ multi
- |= [lock=form no-keys=(z-set schnorr-pubkey)]
- ^+ lock
- %- check
- %- ~(rep z-in no-keys)
- |= [no-key=schnorr-pubkey new-lock=_lock]
- (default new-lock no-key)
- --
- --
- ::
- ++ from-b58
- |= [m=@ pks=(list @t)]
- ^- form
- %- check
- %+ m-of-n:new m
- %- ~(gas z-in *(z-set schnorr-pubkey))
- %+ turn pks
- |= pk=@t
- (from-b58:schnorr-pubkey pk)
- ++ to-b58
- |= loc=form
- ^- [m=@udD pks=(list @t)]
- :- m.loc
- (turn ~(tap z-in pubkeys.loc) to-b58:schnorr-pubkey)
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- |^
- [leaf+m.form (hashable-pubkeys pubkeys.form)]
- ::
- ++ hashable-pubkeys
- |= pubkeys=(z-set schnorr-pubkey)
- ^- hashable:tip5
- ?~ pubkeys leaf+pubkeys
- :+ hash+(hash:schnorr-pubkey n.pubkeys)
- $(pubkeys l.pubkeys)
- $(pubkeys r.pubkeys)
- --
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $nnote: Nockchain note. A UTXO.
- ++ nnote
- =< form
- ~% %nnote ..nnote ~
- |%
- +$ form
- $: $: version=%0 :: utxo version number
- :: the page number in which the note was added to the balance.
- ::NOTE while for dumbnet this could be block-id instead, and that
- ::would simplify some code, for airwalk this would lead to a hashloop
- origin-page=page-number
- :: a note with a null timelock has no page-number restrictions
- :: on when it may be spent
- =timelock
- ==
- ::
- name=nname
- =lock
- =source
- assets=coins
- ==
- ::
- ++ based
- ~/ %based
- |= =form
- ?& (based:nname name.form)
- (based:lock lock.form)
- (based:^hash p.source.form)
- (^based assets.form)
- ==
- ::
- ++ hashable
- ~/ %hashable
- |= =form
- ^- hashable:tip5
- :- :+ leaf+version.form
- leaf+origin-page.form
- hash+(hash:timelock timelock.form)
- :^ hash+(hash:nname name.form)
- hash+(hash:lock lock.form)
- hash+(hash:source source.form)
- leaf+assets.form
- ::
- ++ hash
- ~/ %hash
- |= =form
- %- hash-hashable:tip5
- (hashable form)
- --
- ::
- :: $coinbase: mining reward. special kind of note
- ::
- ++ coinbase
- =< form
- |%
- ++ form
- $~ %* . *nnote
- timelock coinbase-timelock
- is-coinbase.source %.y
- ==
- nnote
- ::
- ++ validate
- |= [pag=page =form]
- ^- ?
- ?. ?& is-coinbase.source.form
- =(p.source.form parent.pag)
- ==
- %.n
- ?: (lth height.pag first-month-coinbase-min)
- =(first-month-coinbase-timelock timelock.form)
- =(coinbase-timelock timelock.form)
- ::
- :: +new: make coinbase for page. not for genesis.
- ++ new
- |= [pag=page lok=lock]
- =/ reward=coins (~(got z-by coinbase.pag) lok)
- ^- form
- =/ =timelock
- ?: (lth height.pag first-month-coinbase-min)
- first-month-coinbase-timelock
- coinbase-timelock
- =/ note=nnote
- %* . *nnote
- assets reward
- lock lok
- timelock timelock
- origin-page height.pag
- name (name-from-parent-hash lok parent.pag height.pag timelock)
- ::
- :: this uses the ID of the parent block to avoid a hashloop in airwalk
- source [parent.pag %.y]
- ==
- ?. (validate pag note)
- ~| %invalid-coinbase
- !!
- note
- ::
- :: +name-from-parent-hash: the name of a coinbase with given owner and parent block.
- ++ name-from-parent-hash
- |= [owners=lock parent-hash=hash height-page=@ =timelock]
- ^- nname
- (new:nname owners [parent-hash %.y] timelock)
- ::
- ++ coinbase-timelock
- ^- timelock
- `[*timelock-range (new:timelock-range [`coinbase-timelock-min ~])]
- ::
- ++ first-month-coinbase-timelock
- ^- timelock
- `[*timelock-range (new:timelock-range [`4.383 ~])]
- ::
- ++ emission-calc
- |= =page-number
- ^- coins
- (schedule:emission `@`page-number)
- --
- ::
- ++ shares
- =< form
- |%
- +$ form $+(shares (z-map lock @))
- ::
- ++ validate
- |= =form
- ?& (lte ~(wyt z-by form) max-coinbase-split)
- ::
- %+ levy ~(tap z-by form)
- |= [=lock s=@]
- ?& !=(s 0)
- (validate:^lock lock)
- ==
- ==
- --
- ::
- :: $coinbase-split: total number of nicks split between mining pubkeys
- ::
- :: despite also being a (z-map lock @), this is not the same thing as .shares
- :: from the mining state. this is the actual number of coins split between the
- :: locks, while .shares is a proportional split used to calculate the actual
- :: number.
- ++ coinbase-split
- =< form
- |%
- +$ form (z-map lock coins)
- ::
- :: +new: construct a coinbase with assets and shares
- :: we assume $shares are already validated inside of the mining state
- ++ new
- |= [assets=coins shares=(z-map lock @)]
- ^- form
- =/ locks=(list lock) ~(tap z-in ~(key z-by shares))
- ?: =(1 (lent locks))
- :: if there is only one pubkey, there is no need to compute a split.
- (~(put z-by *form) (snag 0 locks) assets)
- ::
- =/ split=(list [=lock share=@ =coins])
- %+ turn ~(tap z-by shares)
- |=([=lock s=@] [lock s 0])
- ::
- =| recursion-depth=@
- =/ remaining-coins=coins assets
- =/ total-shares=@
- %+ roll split
- |= [[=lock share=@ =coins] sum=@]
- (add share sum)
- |-
- ?: =(0 remaining-coins)
- (~(gas z-by *form) (turn split |=([l=lock s=@ t=coins] [l t])))
- ?: (gth recursion-depth 2)
- :: we only allow two rounds of recursion to shave microseconds
- :: if any coins are left, we distribute them to the first share
- =/ final-split=(list [lock coins])
- (turn split |=([l=lock s=@ t=coins] [l t]))
- =/ first=[l=lock c=coins] (snag 0 final-split)
- =. c.first (add c.first remaining-coins)
- =. final-split [first (slag 1 final-split)]
- (~(gas z-by *form) final-split)
- :: for each share, calculate coins = (share * total-coins) / total-shares
- :: and track remainders for redistribution
- =/ new-split=(list [=lock share=@ total=coins this=coins])
- %+ turn split
- |= [=lock share=@ current-coins=coins]
- =/ coins-for-share=coins
- (div (mul share remaining-coins) total-shares)
- [lock share (add current-coins coins-for-share) coins-for-share]
- :: calculate what's left to distribute
- =/ distributed=coins
- %+ roll new-split
- |= [[=lock s=@ c=coins this=coins] sum=coins]
- (add this sum)
- ?: =(0 distributed)
- :: if no coins were distributed this round, just give the remainder to
- :: the first share
- =/ final-split=(list [lock coins])
- (turn new-split |=([l=lock s=@ t=coins h=coins] [l t]))
- =/ first=[l=lock c=coins] (snag 0 final-split)
- =. c.first (add c.first remaining-coins)
- =. final-split [first (slag 1 final-split)]
- (~(gas z-by *form) final-split)
- =/ still-remaining=@ (sub remaining-coins distributed)
- %= $
- split (turn new-split |=([l=lock s=@ t=coins h=coins] [l s t]))
- remaining-coins still-remaining
- recursion-depth +(recursion-depth)
- ==
- ::
- :: +based: checks that coinbase split is in base field.
- :: Called by the receiver of a block. In cases where checking for validity
- :: costs the same amount as checking if in field, we check for validity.
- ++ based
- |= =form
- ?. (lte ~(wyt z-by form) max-coinbase-split)
- %|
- %+ levy ~(tap z-by form)
- |= [=lock =coins]
- ~& :* %based-split-check
- coins-not-zero+!=(0 coins)
- coins-based+(^based coins)
- lock-based+(based:^lock lock)
- ==
- ?& !=(0 coins)
- (^based coins)
- (based:^lock lock)
- ==
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- ?~ form leaf+form
- :+ [(hashable:lock p.n.form) leaf+q.n.form]
- $(form l.form)
- $(form r.form)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $seed: carrier of a quantity of assets from an $input to an $output
- ++ seed
- =< form
- |%
- +$ form
- $: :: if non-null, enforces that output note must have precisely
- :: this source
- output-source=(unit source)
- :: the .lock of the output note
- recipient=lock
- :: if non-null, enforces that output note must have precisely
- :: this timelock (though [~ ~ ~] means ~). null means there
- :: is no intent.
- =timelock-intent
- :: quantity of assets gifted to output note
- gift=coins
- :: check that parent hash of every seed is the hash of the
- :: parent note
- parent-hash=^hash
- ==
- ++ new
- =< default
- |%
- ++ default
- |= $: output-source=(unit source)
- recipient=lock
- =timelock-intent
- gift=coins
- parent-hash=^hash
- ==
- %* . *form
- output-source output-source
- recipient recipient
- timelock-intent timelock-intent
- gift gift
- parent-hash parent-hash
- ==
- :: +simple: helper constructor with no timelock intent or output source
- ++ simple
- =< default
- |%
- ++ default
- |= [recipient=lock gift=coins parent-hash=^hash]
- ^- form
- (new *(unit source) recipient *timelock-intent gift parent-hash)
- ::
- :: +from-note: seed sending all coins from a $ to recipient
- ++ from-note
- |= [recipient=lock note=nnote]
- ^- form
- (simple recipient assets.note (hash:nnote note))
- --
- :: delete this? there is no difference between multi and simple cases
- ++ multisig
- =< default
- |%
- ++ default
- |= [recipients=lock gift=coins parent-hash=^hash]
- ^- form
- (new *(unit source) recipients *timelock-intent gift parent-hash)
- ::
- ++ from-note
- |= [recipients=lock note=nnote]
- ^- form
- (multisig recipients assets.note (hash:nnote note))
- --
- --
- ::
- ++ based
- |= =form
- ^- ?
- =/ based-output-source
- ?~ output-source.form
- %&
- (based:^hash p.u.output-source.form)
- ?& based-output-source
- (based:lock recipient.form)
- (based:timelock-intent timelock-intent.form)
- (^based gift.form)
- (based:^hash parent-hash.form)
- ==
- ::
- :: +hashable: we don't include output-source since it could create a hash loop
- ++ hashable
- |= sed=form
- ^- hashable:tip5
- :^ (hashable:lock recipient.sed)
- (hashable:timelock-intent timelock-intent.sed)
- leaf+gift.sed
- hash+parent-hash.sed
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- ++ seeds
- =< form
- ~% %seeds ..seeds ~
- |%
- +$ form (z-set seed)
- ::
- ++ new
- =< default
- |%
- ++ default
- |= seds=(list seed)
- ^- form
- (~(gas z-in *form) seds)
- :: +new-simple-from-note-with-refund: sends gift to recipient and remainder to owner of note
- ++ simple-from-note-with-refund
- =< default
- |%
- :: +default:
- ::
- :: while the sample has a .fee, this is just to account for the size of
- :: the refund. the .fee is stored one level up, in $spend. since this is
- :: a constructor for building a tx with only one note, we necessarily
- :: need to take the fee into account here.
- ++ default
- |= [recipient=lock gift=coins fee=coins note=nnote]
- ^- form
- (with-choice recipient gift fee note lock.note)
- ::
- :: +with-choice: choose the refund address
- ++ with-choice
- |= $: recipient=lock
- gift=coins
- fee=coins
- note=nnote
- refund-address=lock
- ==
- ^- form
- ?> (lte (add gift fee) assets.note)
- =/ refund=coins (sub assets.note (add gift fee))
- =/ gift-seed=seed
- (simple:new:seed recipient gift (hash:nnote note))
- =/ refund-seed=seed
- (multisig:new:seed refund-address refund (hash:nnote note))
- =/ seed-list=(list seed)
- :: if there is no refund, don't use the refund seed
- ?: =(0 refund) ~[gift-seed]
- ~[gift-seed refund-seed]
- (new seed-list)
- --
- --
- ::
- ++ based
- |= =form
- ^- ?
- %+ levy ~(tap z-in form)
- |= s=seed
- (based:seed s)
- ::
- ++ hashable
- |= =form
- ^- hashable:tip5
- ?~ form leaf+form
- :+ (hashable:seed n.form)
- $(form l.form)
- $(form r.form)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $spend: a signed collection of seeds used in an $input
- ::
- :: .signature: expected to be on the hash of the spend's seeds
- ::
- :: .seeds: the individual transfers to individual output notes
- :: that the spender is authorizing
- ++ spend
- =< form
- ~% %spend ..spend ~
- |%
- +$ form
- $: signature=(unit signature)
- :: everything below here is what is hashed for the signature
- =seeds
- fee=coins
- ==
- ::
- ++ new
- =< default
- |%
- ++ default
- |= [=seeds fee=coins]
- %* . *form
- seeds seeds
- fee fee
- ==
- ::
- :: +simple-from-note: generates a $spend sending all assets to recipient from note
- ++ simple-from-note
- =< default
- |%
- ++ default
- |= [recipient=lock note=nnote]
- ^- form
- =; sed=seed (new (~(put z-in *seeds) sed) 0)
- (from-note:simple:new:seed recipient note)
- ::
- :: +with-refund: returns unspent assets to note owner
- ++ with-refund
- =< default
- |%
- ++ default
- |= [recipient=lock gift=coins fee=coins note=nnote]
- ^- form
- =; seds=seeds (new seds fee)
- (simple-from-note-with-refund:new:seeds recipient gift fee note)
- ::
- :: +with-choice: choose which address receives the refund
- ++ with-choice
- |= $: recipient=lock
- gift=coins
- fee=coins
- note=nnote
- refund-address=lock
- ==
- ^- form
- =; seds=seeds (new seds fee)
- %- with-choice:simple-from-note-with-refund:new:seeds
- [recipient gift fee note refund-address]
- --
- --
- --
- ::
- :: +sign: add a single signature to the seeds
- ::
- :: .sen: the $spend we are signing
- :: .sk: the secret key being used to sign
- ++ sign
- ~/ %sign
- |= [sen=form sk=schnorr-seckey]
- ^+ sen
- :: we must derive the pubkey from the seckey
- =/ pk=schnorr-pubkey
- %- ch-scal:affine:curve:cheetah
- :* (t8-to-atom:belt-schnorr:cheetah sk)
- a-gen:curve:cheetah
- ==
- =/ sig=schnorr-signature
- %+ sign:affine:belt-schnorr:cheetah
- sk
- (leaf-sequence:shape (sig-hash sen))
- ?: =(~ signature.sen)
- %_ sen
- signature `(~(put z-by *signature) pk sig)
- ==
- %_ sen
- signature `(~(put z-by (need signature.sen)) pk sig)
- ==
- ::
- :: +verify: verify the .signature and each seed has correct parent-hash
- ++ verify
- ~/ %verify
- |= [sen=form parent-note=nnote]
- ^- ?
- ?~ signature.sen %.n
- =/ parent-hash=hash (hash:nnote parent-note)
- :: check that parent hash of each seed matches the note's hash
- ?. (~(all z-in seeds.sen) |=(sed=seed =(parent-hash.sed parent-hash)))
- %.n
- ::
- =/ have-pks=(z-set schnorr-pubkey) ~(key z-by u.signature.sen)
- :: are there at least as many sigs as m.lock?
- ?: (lth ~(wyt z-in have-pks) m.lock.parent-note)
- %.n
- :: check that the keys in .signature are a subset of the keys in the lock
- ?. =((~(int z-in pubkeys.lock.parent-note) have-pks) have-pks)
- :: =/ base58-have-pks=(list @t)
- :: %+ turn ~(tap z-by have-pks)
- :: to-b58:schnorr-pubkey
- :: =/ base58-pubkeys=(list @t)
- :: %+ turn ~(tap z-by pubkeys.lock.parent-note)
- :: to-b58:schnorr-pubkey
- :: intersection of pubkeys in .lock and pubkeys in .signature does not equal
- :: the pubkeys in .signature
- :: ~& >> "invalid signatures"
- :: ~& >> "have-pks: {<base58-have-pks>}"
- :: ~& >> "pubkeys.lock.parent-note: {<base58-pubkeys>}"
- %.n
- :: we have enough signatures, they're all from the set of pubkeys required
- :: by the lock, so now we can actually verify them.
- ::
- :: we validate all signatures, even if there are more than m, since
- :: saying a transaction is valid with invalid signatures just seems wrong.
- %- ~(all z-in have-pks)
- |= pk=schnorr-pubkey
- %: verify:affine:belt-schnorr:cheetah
- pk
- (leaf-sequence:shape (sig-hash sen))
- (~(got z-by u.signature.sen) pk)
- ==
- ::
- ++ based
- |= sen=form
- ^- ?
- =/ check-sig=?
- ?~(signature.sen %& (based:signature u.signature.sen))
- ?. check-sig
- %|
- ?& (^based fee.sen)
- (based:seeds seeds.sen)
- ==
- ::
- ++ hashable
- |= sen=form
- ^- hashable:tip5
- :+ ?~(signature.sen %leaf^~ [%leaf^~ (hashable:signature u.signature.sen)])
- (hashable:seeds seeds.sen)
- leaf+fee.sen
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- ::
- :: +sig-hash: the hash used for signing and verifying
- ++ sig-hash
- |= sen=form
- ^- hash
- %- hash-hashable:tip5
- [(hashable:seeds seeds.sen) leaf+fee.sen]
- --
- ::
- :: $input: note transfering assets to outputs within a tx
- ::
- :: .note: the note that is transferring assets to outputs within the tx.
- :: the note must exist in the balance in order for it to spend, and it must
- :: be removed from the balance atomically as it spends.
- ::
- :: .spend: authorized commitment to the recipient notes that the input is
- :: transferring assets to and amount of assets given to each output.
- ++ input
- =< form
- ~% %input ..input ~
- |%
- +$ form [note=nnote =spend]
- ::
- ++ new
- =< default
- |%
- ++ default
- |= [not=nnote =seeds fee=coins sk=schnorr-seckey]
- ^- form
- =/ sen
- %+ sign:spend
- (new:spend seeds fee)
- sk
- [not sen]
- ::
- :: +simple-from-note: send all assets in note to recipient
- ++ simple-from-note
- =< default
- |%
- ++ default
- |= [recipient=lock not=nnote sk=schnorr-seckey]
- ^- form
- =/ sen=spend (simple-from-note:new:spend recipient not)
- =. sen
- %+ sign:spend
- sen
- sk
- [not sen]
- ::
- ++ with-refund
- =< default
- |%
- ++ default
- |= [recipient=lock gift=coins fee=coins not=nnote sk=schnorr-seckey]
- ^- form
- =/ sen=spend
- (with-refund:simple-from-note:new:spend recipient gift fee not)
- =. sen
- %+ sign:spend
- sen
- sk
- [not sen]
- ::
- ++ with-choice
- |= $: recipient=lock
- gift=coins
- fee=coins
- not=nnote
- sk=schnorr-seckey
- refund-address=lock
- ==
- ^- form
- =/ sen=spend
- %- with-choice:with-refund:simple-from-note:new:spend
- [recipient gift fee not refund-address]
- =. sen
- %+ sign:spend
- sen
- sk
- [not sen]
- --
- --
- --
- ::
- :: +validate: verifies whether an $input's .spend is valid by checking the sigs
- ++ validate
- ~/ %validate
- |= inp=form
- ^- ?
- =/ check-spend=? (verify:spend spend.inp note.inp)
- =/ check-gifts-and-fee=?
- =/ gifts-and-fee=coins
- %+ add fee.spend.inp
- %+ roll ~(tap z-in seeds.spend.inp)
- |= [=seed acc=coins]
- :(add acc gift.seed)
- =(gifts-and-fee assets.note.inp)
- :: total gifts and fee is = assets in the note (coin scarcity)
- :: ~& >>
- :: :* %validate-input
- :: spend+check-spend
- :: gifts-and-fees+check-gifts-and-fee
- :: ==
- ?&(check-spend check-gifts-and-fee)
- ::
- ++ based
- ~/ %based
- |= inp=form
- &((based:nnote note.inp) (based:spend spend.inp))
- ::
- ++ hashable
- |= inp=form
- ^- hashable:tip5
- :- (hashable:nnote note.inp)
- (hashable:spend spend.inp)
- ::
- ++ hash |=(=form (hash-hashable:tip5 (hashable form)))
- --
- ::
- :: $output: recipient of assets transferred by some inputs in a tx
- ::
- :: .note: the recipient of assets transferred by some inputs in a tx,
- :: and is added to the balance atomically with it receiving assets.
- ::
- :: .seeds: the "carrier" for the individual asset gifts it receives from
- :: each input that chose to spend into it.
- ++ output
- =< form
- ~% %output ..output ~
- |%
- +$ form [note=nnote =seeds]
- ::
- :: +compute-source: computes the source for the note from .seeds
- ::
- :: not to be used for coinbases - use new:coinbase
- ++ compute-source
- |= out=form
- ^- source
- :_ %.n :: is-coinbase
- (hash:seeds seeds.out)
- ::
- ++ validate
- ~/ %validate
- |= out=form
- ^- ?
- =/ source-check=?
- %+ levy ~(tap z-in seeds.out)
- |= =seed
- ?~ output-source.seed %.y
- =(u.output-source.seed source.note.out)
- =/ assets-check=?
- =/ calc-assets=coins
- %+ roll ~(tap z-in seeds.out)
- |= [=seed acc=coins]
- (add gift.seed acc)
- =(calc-assets assets.note.out)
- &(source-check assets-check)
- --
- ::
- :: $tx-acc: accumulator for updating balance while processing txs
- ::
- :: ephemeral struct for incrementally updating balance per tx in a page,
- :: and for accumulating fees and size per tx processed, to be checked
- :: against the coinbase assets and max-page-size
- ++ tx-acc
- =< form
- ~% %tx-acc ..tx-acc ~
- |%
- +$ form
- $: balance=(z-map nname nnote)
- fees=coins
- =size
- txs=(z-set tx)
- ==
- ::
- :: +new: pass in the balance of the parent block to initialize the accumulator for current block
- ++ new
- ~/ %new
- |= bal=(unit (z-map nname nnote))
- :: the unit stuff is to account for the genesis block
- %* . *form
- balance ?~ bal *(z-map nname nnote)
- u.bal
- ==
- ::
- ++ process
- ~/ %process
- |= [tx-acc=form =tx new-page-number=page-number]
- ^- (unit form)
- %- mole
- |.
- ?. (validate:^tx tx new-page-number)
- ~> %slog.[0 leaf+"tx invalid"] !!
- ::
- :: process outputs
- =. balance.tx-acc
- %+ roll ~(val z-by outputs.tx)
- |= [op=output bal=_balance.tx-acc]
- ?: (~(has z-by bal) name.note.op)
- ~> %slog.[0 leaf+"tx output already exists in balance"]
- !!
- (~(put z-by bal) name.note.op note.op)
- ::
- :: process inputs
- =/ [tic=timelock-range tac=form]
- %+ roll ~(val z-by inputs.tx)
- |= [ip=input tic=timelock-range tac=_tx-acc]
- ?. =(`note.ip (~(get z-by balance.tx-acc) name.note.ip))
- ~> %slog.[0 leaf+"tx input does not exist in balance"]
- !!
- =. balance.tac (~(del z-by balance.tac) name.note.ip)
- =. fees.tac (add fees.tac fee.spend.ip)
- =. tic
- %+ merge:timelock-range tic
- %+ fix-absolute:timelock
- timelock.note.ip
- origin-page.note.ip
- [tic tac]
- ::
- ?. (check:timelock-range tic new-page-number)
- ~> %slog.[0 leaf+"failed timelock check"] !!
- ::
- %_ tac
- txs (~(put z-in txs.tac) tx)
- ==
- --
- --
|