| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454 |
- :: /ker/wallet/wallet: nockchain wallet
- /= bip39 /common/bip39
- /= slip10 /common/slip10
- /= m /common/markdown/types
- /= md /common/markdown/markdown
- /= transact /common/tx-engine
- /= z /common/zeke
- /= zo /common/zoon
- /= dumb /apps/dumbnet/lib/types
- /= * /common/zose
- /= * /common/wrapper
- ::
- =>
- =| bug=_&
- |%
- :: $key: public or private key
- ::
- :: both private and public keys are in serialized cheetah point form
- :: they MUST be converted to base58 for export.
- ::
- +$ key
- $~ [%pub p=*@ux]
- $% [%pub p=@ux]
- [%prv p=@ux]
- ==
- :: $coil: key and chaincode
- ::
- :: a wallet consists of a collection of +coil (address and entropy pair). the
- :: $cc (called a chain code elsewhere) allows for the deterministic
- :: generation of child keys from a parent key without compromising other
- :: branches of the hierarchy.
- ::
- :: .key: public or private key
- :: .cc: associated entropy (chain code)
- ::
- +$ coil [%coil =key =cc]
- ::
- :: $meta: stored metadata for a key
- +$ meta
- $% coil
- [%label @t]
- [%seed @t]
- ==
- ::
- :: $keys: path indexed map for keys
- ::
- :: path format for keys state:
- ::
- :: /keys :: root path (holds nothing in its fil)
- :: /keys/[t/master]/[key-type]/m/[coil/key] :: master key path
- :: /keys/[t/master]/[key-type]/[ud/index]/[coil/key] :: derived key path
- :: /keys/[t/master]/[key-type]/[ud/index]/[coil/key] :: specific key path
- :: /keys/[t/master]/[key-type]/[ud/index]/label/[label/label] :: key label path for derived key
- :: /keys/[t/master]/[key-type]/m/label/[label/label] :: key label path for master key
- :: /keys/[t/master]/seed/[seed/seed-phrase] :: seed-phrase path
- ::
- :: Note the terminal entry of the path holds that value, this value is the
- :: non-unit `fil` in the $axal definition
- ::
- :: where:
- :: - [t/master] is the base58 encoded master public key as @t
- :: - m denotes the master key
- :: - [ud/index] is the derivation index as @ud
- :: - [key-type] is either %pub or %prv
- :: - [coil/key] is the key and chaincode pair. key is in serialized
- :: format as a @ux, NOT base58.
- :: - [seed/seed-phrase] is the seed phrase as a tape
- :: - [label/label] is a label value
- ::
- :: master key is stored under 'm'.
- :: derived keys use incrementing indices starting from 0 under their master-key and key-type
- :: labels are stored as children of their associated keys.
- :: seed is a seed phrase and is only stored as a child of [t/master]
- ::
- +$ keys $+(keys-axal (axal meta))
- ::
- :: $draft-tree: structured tree of draft, input, and seed data
- ::
- :: we use the axal structure to track the relationship between drafts,
- :: inputs, and seeds. this allows us to navigate the tree and maintain
- :: all the relationships without duplicating data.
- ::
- :: paths in the draft-tree follow these conventions:
- ::
- :: /draft/[draft-name] :: draft node
- :: /draft/[draft-name]/input/[input-name] :: input in a draft
- :: /input/[input-name] :: input node
- :: /input/[input-name]/seed/[seed-name] :: seed in an input
- :: /seed/[seed-name] :: seed node
- ::
- +$ draft-tree
- $+ wallet-draft-tree
- (axal draft-entity)
- :: $draft-entity: entities stored in the draft tree
- ::
- +$ draft-entity
- $% [%draft =draft-name =draft]
- [%input =input-name =preinput]
- [%seed =seed-name =preseed]
- ==
- ::
- :: +master: master key pair
- ++ master
- =< form
- |%
- +$ form (unit coil)
- ++ public
- |= =form
- ?: ?=(^ form)
- u.form
- ~|("master public key not found" !!)
- ::
- ++ to-b58
- |= =form
- ^- @t
- (crip (en:base58:wrap p.key:(public form)))
- --
- :: $cc: chaincode
- ::
- +$ cc @ux
- :: $balance: wallet balance
- +$ balance
- $+ wallet-balance
- (z-map:zo nname:transact nnote:transact)
- :: $state: wallet state
- ::
- +$ state
- $: %0
- =balance
- hash-to-name=(z-map:zo hash:transact nname:transact) :: hash of note -> name of note
- name-to-hash=(z-map:zo nname:transact hash:transact) :: name of note -> hash of note
- receive-address=lock:transact
- =master
- =keys
- transactions=$+(transactions (map * transaction))
- last-block=(unit block-id:transact)
- peek-requests=$+(peek-requests (map @ud ?(%balance %block)))
- active-draft=(unit draft-name)
- active-input=(unit input-name)
- active-seed=(unit seed-name) :: currently selected seed
- draft-tree=draft-tree :: structured tree of drafts, inputs, and seeds
- pending-commands=(z-map:zo @ud [phase=?(%block %balance %ready) wrapped=cause]) :: commands waiting for sync
- ==
- +$ seed-name $~('default-seed' @t)
- ::
- +$ draft-name $~('default-draft' @t)
- ::
- +$ input-name $~('default-input' @t)
- ::
- :: $transaction: TODO
- ::
- +$ transaction
- $: recipient=@ux
- amount=@ud
- status=?(%unsigned %signed %sent)
- ==
- ::
- +$ cause
- $% [%keygen entropy=byts salt=byts]
- [%derive-child key-type=?(%pub %prv) i=@ label=(unit @t)]
- [%import-keys keys=(list (pair trek meta))]
- [%export-keys ~]
- [%export-master-pubkey ~]
- [%import-master-pubkey =coil] :: base58-encoded pubkey + chain code
- [%make-tx dat=draft]
- [%list-notes-by-pubkey pubkey=@t] :: base58-encoded pubkey
- $: %simple-spend
- names=(list [first=@t last=@t]) :: base58-encoded name hashes
- recipients=(list [m=@ pks=(list @t)]) :: base58-encoded locks
- gifts=(list coins:transact) :: number of coins to spend
- fee=coins:transact :: fee
- ==
- [%sign-tx dat=draft index=(unit @ud) entropy=@]
- [%list-pubkeys ~]
- [%list-notes ~]
- [%show-seedphrase ~]
- [%show-master-pubkey ~]
- [%show-master-privkey ~]
- [%show =path]
- [%gen-master-privkey seedphrase=@t]
- [%gen-master-pubkey master-privkey=keyc:slip10]
- [%update-balance ~]
- [%update-block ~]
- [%sync-run wrapped=cause] :: run command after sync completes
- $: %scan
- master-pubkey=@t :: base58 encoded master public key to scan for
- search-depth=$~(100 @ud) :: how many addresses to scan (default 100)
- include-timelocks=$~(%.n ?) :: include timelocked notes (default false)
- include-multisig=$~(%.n ?) :: include notes with multisigs (default false)
- ==
- [%advanced-spend advanced-spend]
- [%file %write path=@t contents=@t success=?]
- npc-cause
- ==
- ::
- +$ advanced-spend
- $% [%seed advanced-spend-seed]
- [%input advanced-spend-input]
- [%draft advanced-spend-draft]
- ==
- ::
- +$ advanced-spend-seed
- $% [%new name=@t] :: new empty seed in draft
- $: %set-name
- seed-name=@t
- new-name=@t
- ==
- $: %set-source :: set .output-source
- seed-name=@t
- source=(unit [hash=@t is-coinbase=?])
- ==
- $: %set-recipient :: set .recipient
- seed-name=@t
- recipient=[m=@ pks=(list @t)]
- ==
- $: %set-timelock :: set .timelock-intent
- seed-name=@t
- absolute=timelock-range:transact
- relative=timelock-range:transact
- ==
- $: %set-gift
- seed-name=@t
- gift=coins:transact
- ==
- $: %set-parent-hash
- seed-name=@t
- parent-hash=@t
- ==
- $: %set-parent-hash-from-name
- seed-name=@t
- name=[@t @t]
- ==
- $: %print-status :: do the needful
- seed-name=@t
- ==
- ==
- :: $seed-mask: tracks which fields of a $seed:transact have been set
- ::
- :: this might have been better as a "unitized seed" but would have been
- :: much more annoying to read the code
- +$ seed-mask
- $~ [%.n %.n %.n %.n %.n]
- $: output-source=?
- recipient=?
- timelock-intent=?
- gift=?
- parent-hash=?
- ==
- :: $preseed: a $seed:transact in process of being built
- +$ preseed [name=@t (pair seed:transact seed-mask)]
- ::
- :: $spend-mask: tracks which field of a $spend:transact have been set
- +$ spend-mask
- $~ [%.n %.n %.n]
- $: signature=?
- seeds=?
- fee=?
- ==
- ::
- +$ advanced-spend-input
- :: there is only one right way to create an $input from a $spend, so we don't need
- :: the mask or other commands.
- $% [%new name=@t] :: new empty input
- $: %set-name
- input-name=@t
- new-name=@t
- ==
- $: %add-seed
- input-name=@t
- seed-name=@t
- ==
- $: %set-fee
- input-name=@t
- fee=coins:transact
- ==
- $: %set-note-from-name :: set .note using .name
- input-name=@t
- name=[@t @t]
- ==
- $: %set-note-from-hash :: set .note using hash
- input-name=@t
- hash=@t
- ==
- $: %derive-note-from-seeds :: derive note from seeds
- input-name=@t
- ==
- $: %remove-seed
- input-name=@t
- seed-name=@t
- ==
- $: %remove-seed-by-hash
- input-name=@t
- hash=@t
- ==
- $: %print-status
- input-name=@t
- ==
- ==
- ::
- +$ input-mask
- $~ [%.n *spend-mask]
- $: note=?
- spend=spend-mask
- ==
- ::
- +$ preinput [name=@t (pair input:transact input-mask)]
- ::
- +$ draft [name=@t p=inputs:transact]
- ::
- +$ advanced-spend-draft
- $% [%new name=@t] :: new input draft
- $: %set-name
- draft-name=@t
- new-name=@t
- ==
- $: %add-input
- draft-name=@t
- input-name=@t
- ==
- $: %remove-input
- draft-name=@t
- input-name=@t
- ==
- $: %remove-input-by-name
- draft-name=@t
- name=[first=@t last=@t]
- ==
- [%print-status =draft-name] :: print draft status
- ==
- ::
- +$ npc-cause
- $% [%npc-bind pid=@ result=*]
- ==
- ::
- +$ effect
- $~ [%npc 0 %poke %fact *fact:dumb]
- $% file-effect
- [%markdown @t]
- [%raw *]
- [%npc pid=@ npc-effect]
- [%exit code=@]
- ==
- ::
- +$ file-effect
- $%
- [%file %read path=@t]
- [%file %write path=@t contents=@]
- ==
- ::
- +$ npc-effect
- $% [%poke $>(%fact cause:dumb)]
- [%peek path]
- ==
- ::
- ::TODO this probably shouldnt live in here
- ::
- ++ print
- |= nodes=markdown:m
- ^- (list effect)
- ~[(make-markdown-effect nodes)]
- ::
- ++ warn
- |* meg=tape
- |* *
- ?. bug +<
- ~> %slog.[1 (cat 3 'wallet: warning: ' (crip meg))]
- +<
- ::
- ++ debug
- |* meg=tape
- |* *
- ?. bug +<
- ~> %slog.[2 (cat 3 'wallet: debug: ' (crip meg))]
- +<
- ::
- ++ moat (keep state)
- ::
- ::
- :: +edit: modify inputs
- ++ edit
- |_ =state
- ::
- +* inp
- ^- preinput
- ?~ active-input.state
- %- (debug "no active input set!")
- *preinput
- =/ input-result (~(get-input plan draft-tree.state) u.active-input.state)
- ?~ input-result
- ~|("active input not found in draft-tree" !!)
- u.input-result
- :: +add-seed: add a seed to the input
- ::
- ++ add-seed
- |= =seed:transact
- ^- [(list effect) ^state]
- ?: (~(has z-in:zo seeds.spend.p.inp) seed)
- :_ state
- %- print
- %- need
- %- de:md
- %- crip
- """
- ## add-seed
- **seed already exists in .spend**
- """
- =/ pre=preinput inp
- =/ =preinput
- %= pre
- seeds.spend.p
- %. seed
- ~(put z-in:zo seeds.spend.p.pre)
- ::
- seeds.spend.q %.y
- ==
- =. active-input.state (some name.pre)
- ::
- =/ input-name=input-name (need active-input.state)
- =. draft-tree.state
- (~(add-input plan draft-tree.state) input-name preinput)
- :: if active-seed is set, link it to this input
- =. draft-tree.state
- ?: ?=(^ active-seed.state)
- (~(link-seed-to-input plan draft-tree.state) input-name u.active-seed.state)
- draft-tree.state
- `state
- ::
- ++ remove-seed
- |= =seed:transact
- ^- [(list effect) ^state]
- ?. (~(has z-in:zo seeds.spend.p.inp) seed)
- :_ state
- %- print
- %- need
- %- de:md
- %- crip
- """
- ## remove-seed
- **seed not found in .spend**
- """
- =/ pre=preinput inp
- =. seeds.spend.p.pre
- %. seed
- ~(del z-in:zo seeds.spend.p.pre)
- =. draft-tree.state
- =/ input-name=input-name (need active-input.state)
- (~(add-input plan draft-tree.state) input-name pre)
- `state
- --
- ::
- :: +draw: modify drafts
- ++ draw
- |_ =state
- +* df
- ^- draft
- ?> ?=(^ active-draft.state)
- =/ draft-result (~(get-draft plan draft-tree.state) u.active-draft.state)
- ?~ draft-result
- *draft
- u.draft-result
- :: +add-input: add an input to the draft
- ::
- ++ add-input
- |= =input:transact
- ^- [(list effect) ^state]
- =/ =draft df
- =/ =input-name
- =+ (to-b58:nname:transact name.note.input)
- %- crip
- "{<first>}-{<last>}"
- ?: (~(has z-by:zo p.df) name.note.input)
- :_ state
- %- print
- %- need
- %- de:md
- %- crip
- """
- ## add-input
- **input already exists in .draft**
- draft already has input with note name: {<input-name>}
- """
- =/ active-draft=draft-name (need active-draft.state)
- =. p.draft
- %- ~(put z-by:zo p.draft)
- :- name.note.input
- input
- =. draft-tree.state
- %. [active-draft draft]
- ~(add-draft plan draft-tree.state)
- =. draft-tree.state
- %. [active-draft input-name]
- ~(link-input-to-draft plan draft-tree.state)
- write-draft
- ::
- ++ write-draft
- ^- [(list effect) ^state]
- =? active-draft.state ?=(~ active-draft.state) (some *draft-name)
- ?> ?=(^ active-draft.state)
- =/ =draft df
- =. draft-tree.state (~(add-draft plan draft-tree.state) u.active-draft.state draft)
- =/ dat-jam (jam draft)
- =/ path=@t (crip "drafts/{(trip u.active-draft.state)}.draft")
- =/ effect [%file %write path dat-jam]
- :_ state
- ~[effect [%exit 0]]
- --
- ::
- :: Convenience wrapper door for slip10 library
- :: ** Never use slip10 directly in the wallet **
- ++ s10
- |_ bas=base:slip10
- ++ gen-master-key
- |= [entropy=byts salt=byts]
- =/ argon-byts=byts
- :- 32
- %+ argon2-nockchain:argon2:crypto
- entropy
- salt
- =/ memo=tape (from-entropy:bip39 argon-byts)
- %- (debug "memo: {memo}")
- :- (crip memo)
- (from-seed:slip10 [64 (to-seed:bip39 memo "")])
- ::
- ++ from-seed
- |= =byts
- (from-seed:slip10 byts)
- ::
- ++ from-private
- |= =keyc:slip10
- (from-private:slip10 keyc)
- ::
- ++ from-public
- |= =keyc:slip10
- (from-public:slip10 keyc)
- ::
- :: Derives public key from parent public key
- :: index i is expected to be a bip32 style index
- :: meaning that for the n-th child key, i=n.
- ::
- ++ derive-public
- |= [parent=coil i=@u]
- ?> &(?=(%pub -.key.parent) (lte i (dec (bex 31))))
- => [cor=(from-public [p.key cc]:parent) i=i]
- (derive-public:cor i)
- ::
- :: Derives private key from parent private key
- :: index i is expected to be a bip32 style index
- :: meaning that for n-th child key: i = (n + 2^31)
- ::
- ++ derive-private
- |= [parent=coil i=@u]
- ?> &(?=(%prv -.key.parent) (gte i (bex 31)))
- => [cor=(from-private [p.key cc]:parent) i=i]
- (derive-private:cor i)
- --
- ::
- ++ vault
- |_ =state
- ++ base-path ^- trek
- ?~ master.state
- ~|("base path not accessible because master not set" !!)
- /keys/[t/(to-b58:master master.state)]
- ::
- ++ seed-path ^- trek
- (welp base-path /seed)
- ::
- ++ get
- |_ key-type=?(%pub %prv)
- ::
- ++ key-path ^- trek
- (welp base-path ~[key-type])
- ::
- ++ seed-path ^- trek
- (welp base-path /seed)
- ::
- ++ master
- ^- coil
- =/ =trek (welp key-path /m)
- =/ =meta (~(got of keys.state) trek)
- ?> ?=(%coil -.meta)
- meta
- ::
- ++ by-index
- |= index=@ud
- ^- coil
- =/ =trek (welp key-path /[ud/index])
- =/ =meta (~(got of keys.state) trek)
- ?> ?=(%coil -.meta)
- meta
- ::
- ++ seed
- ^- meta
- (~(got of keys.state) seed-path)
- ::
- ++ by-label
- |= label=@t
- %+ murn keys
- |= [t=trek =meta]
- ?:(&(?=(%label -.meta) =(label +.meta)) `t ~)
- ::
- ++ keys
- ^- (list [trek meta])
- =/ subtree
- %- ~(kids of keys.state)
- key-path
- ~(tap by kid.subtree)
- ::
- ++ coils
- ^- (list coil)
- %+ murn keys
- |= [t=trek =meta]
- ^- (unit coil)
- ;; (unit coil)
- ?:(=(%coil -.meta) `meta ~)
- --
- ::
- ++ put
- |%
- ::
- ++ seed
- |= seed-phrase=@t
- ^- (axal meta)
- %- ~(put of keys.state)
- [seed-path [%seed seed-phrase]]
- ::
- ++ key
- |= [=coil index=(unit @) label=(unit @t)]
- ^- (axal meta)
- =/ key-type=@tas -.key.coil
- =/ suffix=trek
- ?@ index
- /[key-type]/m
- /[key-type]/[ud/u.index]
- =/ key-path=trek (welp base-path suffix)
- %- (debug "adding key at {(en-tape:trek key-path)}")
- =. keys.state (~(put of keys.state) key-path coil)
- ?~ label
- keys.state
- %+ ~(put of keys.state)
- (welp key-path /label)
- label/u.label
- --
- ::
- :: +derive-child: derives the i-th hardened/unhardened child
- ::
- :: derives the i-th hardened or unhardened child from the master key.
- :: this arm will convert i to fit the slip10/bip32 indexing schemes.
- :: the i-th hardened corresponds to index i + 2^31 while the ith
- :: unhardened child corresponds to index i.
- ::
- ++ derive-child
- |= [parent=coil i=@u]
- ^- coil
- ?: (gte i (bex 31))
- ~|("Child index {<i>} out of range. Child indices are capped to values between [0, 2^31)" !!)
- ?~ master.state
- ~|("No master keys available for derivation" !!)
- ?: ?=(%prv -.key.parent)
- ::
- :: If the parent key is %prv, then the child is hardened
- :: and we add 2^31 to the index
- => (derive-private:s10 parent (add i (bex 31)))
- [%coil [%prv private-key] chain-code]
- => (derive-public:s10 parent i)
- [%coil [%pub public-key] chain-code]
- ::
- ++ get-note
- |= name=nname:transact
- ^- nnote:transact
- ?: (~(has z-by:zo balance.state) name)
- (~(got z-by:zo balance.state) name)
- ~|("note not found: {<name>}" !!)
- ::
- ++ get-note-from-hash
- |= has=hash:transact
- ^- nnote:transact
- =/ name=nname:transact
- (~(got z-by:zo hash-to-name.state) has)
- (get-note name)
- ::
- ++ generate-pid
- |= peek-type=?(%balance %block)
- ^- (unit @ud)
- =/ has-active-peek=?
- %- ~(any by peek-requests.state)
- |=(t=?(%balance %block) =(t peek-type))
- ?: has-active-peek ~
- =/ used-pids=(list @ud)
- ~(tap in ~(key by peek-requests.state))
- =/ max-pid=@ud
- (roll used-pids max)
- =/ next-pid=@ud +(max-pid)
- ?: =(next-pid 0) `1 :: handle wraparound
- `next-pid
- --
- :: +plan: core for managing draft relationships
- ::
- :: provides methods for adding, removing, and navigating the draft tree.
- :: uses the axal structure to maintain relationships between drafts, inputs,
- :: and seeds.
- ::
- ++ plan
- |_ tree=draft-tree
- ::
- :: +get-draft: retrieve a draft by name
- ::
- ++ get-draft
- |= name=draft-name
- ^- (unit draft)
- =/ res (~(get of tree) /draft/[name])
- ?~ res ~
- ?. ?=(%draft -.u.res) ~
- `draft.u.res
- :: +get-input: retrieve an input by name
- ::
- ++ get-input
- |= name=input-name
- ^- (unit preinput)
- =/ res (~(get of tree) /input/[name])
- ?~ res ~
- ?. ?=(%input -.u.res) ~
- `preinput.u.res
- :: +get-seed: retrieve a seed by name
- ::
- ++ get-seed
- |= name=seed-name
- ^- (unit preseed)
- =/ res (~(get of tree) /seed/[name])
- ?~ res ~
- ?. ?=(%seed -.u.res) ~
- `preseed.u.res
- :: +add-draft: add a new draft
- ::
- ++ add-draft
- |= [name=draft-name =draft]
- ^- draft-tree
- =/ entity [%draft name draft]
- (~(put of tree) /draft/[name] entity)
- :: +add-input: add a new input
- ::
- ++ add-input
- |= [name=input-name =preinput]
- ^- draft-tree
- =/ entity [%input name preinput]
- (~(put of tree) /input/[name] entity)
- :: +add-seed: add a new seed
- ::
- ++ add-seed
- |= [name=seed-name =preseed]
- ^- draft-tree
- =/ entity [%seed name preseed]
- (~(put of tree) /seed/[name] entity)
- :: +link-input-to-draft: link an input to a draft
- ::
- ++ link-input-to-draft
- |= [draft-name=draft-name input-name=input-name]
- ^- draft-tree
- =/ input-entity (~(get of tree) /input/[input-name])
- ?~ input-entity tree
- ?. ?=(%input -.u.input-entity) tree
- (~(put of tree) /draft/[draft-name]/input/[input-name] u.input-entity)
- :: +link-seed-to-input: link a seed to an input
- ::
- ++ link-seed-to-input
- |= [input-name=input-name seed-name=seed-name]
- ^- draft-tree
- =/ seed-entity (~(get of tree) /seed/[seed-name])
- ?~ seed-entity tree
- ?. ?=(%seed -.u.seed-entity) tree
- (~(put of tree) /input/[input-name]/seed/[seed-name] u.seed-entity)
- :: +unlink-input-from-draft: remove an input from a draft
- ::
- ++ unlink-input-from-draft
- |= [draft-name=draft-name input-name=input-name]
- ^- draft-tree
- (~(del of tree) /draft/[draft-name]/input/[input-name])
- :: +unlink-seed-from-input: remove a seed from an input
- ::
- ++ unlink-seed-from-input
- |= [input-name=input-name seed-name=seed-name]
- ^- draft-tree
- (~(del of tree) /input/[input-name]/seed/[seed-name])
- :: +list-draft-inputs: list all inputs in a draft
- ::
- ++ list-draft-inputs
- |= name=draft-name
- ^- (list input-name)
- =/ kids (~(kid of tree) /draft/[name])
- %+ murn ~(tap in ~(key by kids))
- |= pax=pith
- ^- (unit input-name)
- =/ pax=path (pout pax)
- ?> ?=([%input *] pax)
- ?> ?=(^ t.pax)
- `i.t.pax
- :: +list-input-seeds: list all seeds in an input
- ::
- ++ list-input-seeds
- |= name=input-name
- ^- (list seed-name)
- =/ kids (~(kid of tree) /input/[name])
- %+ murn ~(tap in ~(key by kids))
- |= pax=pith
- ^- (unit seed-name)
- =/ pax=path (pout pax)
- ?: &(?=([%seed *] pax) ?=(^ t.pax))
- `i.t.pax
- ~
- :: +list-all-drafts: list all draft names
- ::
- ++ list-all-drafts
- ^- (list draft-name)
- =/ kids (~(kid of tree) /draft)
- %+ murn ~(tap in ~(key by kids))
- |= pax=pith
- ^- (unit draft-name)
- =/ pax=path (pout pax)
- ?: ?=(^ pax)
- `i.pax
- ~
- :: +list-all-inputs: list all input names
- ::
- ++ list-all-inputs
- ^- (list input-name)
- =/ kids (~(kid of tree) /input)
- %+ murn ~(tap in ~(key by kids))
- |= pax=pith
- ^- (unit input-name)
- =/ pax=path (pout pax)
- ?: ?=(^ pax)
- `i.pax
- ~
- :: +list-all-seeds: list all seed names
- ::
- ++ list-all-seeds
- ^- (list seed-name)
- =/ kids (~(kid of tree) /seed)
- %+ murn ~(tap in ~(key by kids))
- |= pax=pith
- ^- (unit seed-name)
- =/ pax=path (pout pax)
- ?: &(?=([%seed *] pax) ?=(^ t.pax))
- `i.pax
- ~
- :: +remove-draft: completely remove a draft and its associations
- ::
- ++ remove-draft
- |= name=draft-name
- ^- draft-tree
- (~(lop of tree) /draft/[name])
- :: +remove-input: completely remove an input and its associations
- ::
- ++ remove-input
- |= name=input-name
- ^- draft-tree
- (~(lop of tree) /input/[name])
- :: +remove-seed: completely remove a seed and its associations
- ::
- ++ remove-seed
- |= name=seed-name
- ^- draft-tree
- (~(lop of tree) /seed/[name])
- --
- ::
- ++ make-markdown-effect
- |= nodes=markdown:m
- [%markdown (crip (en:md nodes))]
- ::
- ++ display-poke
- |= =cause
- ^- effect
- =/ nodes=markdown:m
- %- need
- %- de:md
- %- crip
- """
- ## poke
- {<cause>}
- """
- (make-markdown-effect nodes)
- ::
- ++ display-note
- |= note=nnote:transact
- ^- markdown:m
- %- need
- %- de:md
- %- crip
- """
- ## details
- - name: {<(to-b58:nname:transact name.note)>}
- - assets: {<assets.note>}
- - source: {<source.note>}
- ## lock
- - m: {<m.lock.note>}
- - signers: {<(to-b58:signers:lock:transact lock.note)>}
- """
- ::
- ++ show
- |= [=state =path]
- ^- [(list effect) ^state]
- |^
- ?+ path !!
- [%balance ~]
- :- ~[(display-balance balance.state)]
- state
- ::
- [%receive-address ~]
- :- (display-receive-address receive-address.state)
- state
- ::
- [%state ~]
- :- display-state
- state
- ==
- ++ display-balance
- |= =balance
- ^- effect
- =/ nodes=markdown:m
- %- need
- %- de:md
- %- crip
- """
- ## balance
- {<balance>}
- """
- (make-markdown-effect nodes)
- ::
- ++ display-receive-address
- |= address=lock:transact
- ^- (list effect)
- =/ nodes=markdown:m
- %- need
- %- de:md
- %- crip
- """
- ## receive address
- {<address>}
- """
- ~[(make-markdown-effect nodes)]
- ::
- ++ display-state
- ^- (list effect)
- =/ nodes=markdown:m
- %- need
- %- de:md
- %- crip
- """
- ## state
- - last block: {<last-block.state>}
- """
- ~[(make-markdown-effect nodes)]
- --
- --
- ::
- %- (moat &)
- ^- fort:moat
- |_ =state
- +* v ~(. vault state)
- d ~(. draw state)
- e ~(. edit state)
- p ~(. plan draft-tree.state)
- ::
- ++ load
- |= arg=^state
- ^- ^state
- arg
- ::
- ++ peek
- |= =path
- ^- (unit (unit *))
- %- (debug "peek: {<state>}")
- ?+ path ~
- ::
- [%balance ~]
- ``balance.state
- ::
- [%receive-address ~]
- ``receive-address.state
- ::
- [%state ~]
- ``state
- ==
- ::
- ++ poke
- |= =ovum:moat
- |^
- ^- [(list effect) ^state]
- %- (warn "debug printing may expose sensitive information")
- =/ =wire wire.ovum
- =+ [@ src=@ta ver=@ rest=*]=wire.ovum
- %- (debug "wire: {<[src ver `path`rest]>}")
- =/ cause=(unit cause)
- %- (soft cause)
- cause.input.ovum
- =/ success [%exit 0]
- =/ failure [%exit 1]
- ?~ cause
- %- (debug "input does not have a proper cause: {<cause.input.ovum>}")
- [~[failure] state]
- =/ cause u.cause
- %- (debug "cause: {<-.cause>}")
- =; process
- =^ effs state process
- :: check for pending balance commands and execute them
- =^ pending-effs state handle-pending-commands
- [(weld effs pending-effs) state]
- ?- -.cause
- %npc-bind (handle-npc cause)
- %show (show state path.cause)
- %keygen (do-keygen cause)
- %derive-child (do-derive-child cause)
- %sign-tx (do-sign-tx cause)
- %scan (do-scan cause)
- %list-notes (do-list-notes cause)
- %list-notes-by-pubkey (do-list-notes-by-pubkey cause)
- %simple-spend (do-simple-spend cause)
- %update-balance (do-update-balance cause)
- %update-block (do-update-block cause)
- %import-keys (do-import-keys cause)
- %export-keys (do-export-keys cause)
- %export-master-pubkey (do-export-master-pubkey cause)
- %import-master-pubkey (do-import-master-pubkey cause)
- %gen-master-privkey (do-gen-master-privkey cause)
- %gen-master-pubkey (do-gen-master-pubkey cause)
- %make-tx (do-make-tx cause)
- %list-pubkeys (do-list-pubkeys cause)
- %sync-run (do-sync-run cause)
- %show-seedphrase (do-show-seedphrase cause)
- %show-master-pubkey (do-show-master-pubkey cause)
- %show-master-privkey (do-show-master-privkey cause)
- ::
- %advanced-spend
- ?- +<.cause
- %seed (do-advanced-spend-seed +>.cause)
- %input (do-advanced-spend-input +>.cause)
- %draft (do-advanced-spend-draft +>.cause)
- ==
- ::
- %file
- ?> ?=(%write +<.cause)
- ~& > "%file %write: {<cause>}"
- [[%exit 0]~ state]
- ==
- ::
- ++ handle-npc
- |= =npc-cause
- ^- [(list effect) ^state]
- %- (debug "handle-npc: {<-.npc-cause>}")
- ?- -.npc-cause
- %npc-bind
- =/ pid pid.npc-cause
- =/ peek-type
- ?. (~(has by peek-requests.state) pid)
- ~|("no peek request found for pid: {<pid>}" !!)
- (~(got by peek-requests.state) pid)
- =/ result result.npc-cause
- ?- peek-type
- %balance
- =/ softed=(unit (unit (unit (z-map:zo nname:transact nnote:transact))))
- %- (soft (unit (unit (z-map:zo nname:transact nnote:transact))))
- result
- =. peek-requests.state
- (~(del by peek-requests.state) pid)
- ?~ softed
- %- (debug "handle-npc: %balance: could not soft result")
- [[%exit 0]~ state]
- =/ balance-result=(unit (unit _balance.state)) u.softed
- ?~ balance-result
- %- (warn "%update-balance did not return a result: bad path")
- [[%exit 0]~ state]
- ?~ u.balance-result
- %- (warn "%update-balance did not return a result: nothing")
- [[%exit 0]~ state]
- ?~ u.u.balance-result
- %- (warn "%update-balance did not return a result: empty result")
- [[%exit 0]~ state]
- =. balance.state u.u.balance-result
- %- (debug "balance state updated!")
- :: count the number of notes in the balance
- =/ note-count=@ud
- (lent ~(tap z-by:zo balance.state))
- %- (debug "note count: {<note-count>}")
- =. name-to-hash.state
- %- ~(run z-by:zo balance.state)
- |= not=nnote:transact
- ^- hash:transact
- (hash:nnote:transact not)
- =. hash-to-name.state
- %- ~(gas z-by:zo *(z-map:zo hash:transact nname:transact))
- %+ turn ~(tap z-by:zo name-to-hash.state)
- |= [name=nname:transact =hash:transact]
- [hash name]
- :: move each command from balance phase to ready phase
- =/ balance-commands=(list [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]])
- %+ skim ~(tap z-by:zo pending-commands.state)
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- =(phase %balance)
- =. pending-commands.state
- %- ~(gas z-by:zo pending-commands.state)
- %+ turn balance-commands
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- [pid [%ready wrapped]]
- ::
- :: the top-level poke arm should check for pending commands
- :: and execute them as appropriate
- %- (debug "handle-npc: %balance: balance updated, pending commands ready for execution")
- [~ state]
- ::
- %block
- %- (debug "handle-npc: %block")
- =/ softed=(unit (unit (unit (unit block-id:transact))))
- %- (soft (unit (unit (unit block-id:transact))))
- result
- =. peek-requests.state
- (~(del by peek-requests.state) pid)
- ?~ softed
- %- (warn "handle-npc: %block: could not soft result")
- [[%exit 0]~ state]
- =/ block-result u.softed
- %- (debug "handle-npc: %block: {<block-result>}")
- %- (debug "handle-npc: %block: {<peek-requests.state>}")
- ?~ block-result
- %- (warn "handle-npc: %block: bad path")
- [[%exit 0]~ state]
- ?~ u.block-result
- %- (warn "handle-npc: %block: nothing")
- [[%exit 0]~ state]
- ?~ u.u.block-result
- %- (warn "handle-npc: %block: empty result")
- [[%exit 0]~ state]
- %- (debug "handle-npc: %block: found block")
- %- (debug "handle-npc: {<(to-b58:block-id:transact (need u.u.block-result))>}")
- %- (debug "handle-npc: hash: {<(to-b58:hash:transact (need u.u.block-result))>}")
- =. last-block.state u.u.block-result
- :: move each command from block phase to balance phase
- =/ block-commands=(list [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]])
- %+ skim ~(tap z-by:zo pending-commands.state)
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- =(phase %block)
- ::
- %- (debug "handle-npc: %block: preparing {<(lent block-commands)>} commands for balance update")
- :: move each command to balance update phase
- =. pending-commands.state
- %- ~(gas z-by:zo pending-commands.state)
- %+ turn block-commands
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- [pid [%balance wrapped]]
- :: check if we need to update balance (if there are commands waiting for it)
- =/ have-balance-cmds=?
- %- ~(any z-by:zo pending-commands.state)
- |= [phase=?(%block %balance %ready) wrapped=*]
- =(phase %balance)
- ::
- ?: have-balance-cmds
- %- (debug "handle-npc: %block: initiating balance update for pending commands")
- =^ balance-update-effs state
- (do-update-balance [%update-balance ~])
- [balance-update-effs state]
- `state
- ::
- ==
- ==
- ::
- ++ handle-pending-commands
- ^- [(list effect) ^state]
- =/ ready-commands=(list [pid=@ud =cause])
- %+ turn
- %+ skim ~(tap z-by:zo pending-commands.state)
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- =(phase %ready)
- |= [pid=@ud [phase=?(%block %balance %ready) wrapped=cause]]
- [pid wrapped]
- ?~ ready-commands
- %- (debug "no pending commands to execute")
- `state
- %- (debug "executing {<(lent ready-commands)>} pending commands")
- :: process each command
- =/ effs=(list effect) ~
- =/ cmds=(list [pid=@ud =cause]) ready-commands
- |-
- ?~ cmds
- [effs state]
- =/ pid=@ud pid.i.cmds
- =/ =cause +.i.cmds
- =/ ov=^ovum
- %* . ovum
- cause.input cause
- ==
- =. pending-commands.state
- (~(del z-by:zo pending-commands.state) pid)
- ::
- =^ cmd-effs state
- (poke ov)
- $(cmds t.cmds, effs (weld effs cmd-effs))
- ::
- ++ do-sync-run
- |= =cause
- ?> ?=(%sync-run -.cause)
- %- (debug "sync-run: wrapped command {<-.wrapped.cause>}")
- :: get a new pid for the command
- =/ pid=(unit @ud) (generate-pid:v %block)
- ?~ pid
- :: if we can't get a pid, run the command directly
- %- (debug "sync-run: no pid available, running command directly")
- =/ ov=^ovum
- %* . ovum
- cause.input wrapped.cause
- ==
- (poke ov)
- :: store the command in pending-commands
- =. pending-commands.state
- %+ ~(put z-by:zo pending-commands.state)
- u.pid
- [%block wrapped.cause]
- :: initiate block update
- =^ block-update-effs state
- (do-update-block [%update-block ~])
- :: return block update effects
- [block-update-effs state]
- ::
- ++ do-update-balance
- |= =cause
- ?> ?=(%update-balance -.cause)
- %- (debug "update-balance")
- %- (debug "last balance size: {<(lent ~(tap z-by:zo balance.state))>}")
- =/ pid=(unit @ud) (generate-pid:v %balance)
- ?~ pid `state
- =/ bid=block-id:transact
- ?: ?=(~ last-block.state)
- ~|("no last block found, not updating balance" !!)
- (need last-block.state)
- =/ =path (snoc /balance (to-b58:block-id:transact bid))
- =/ =effect [%npc u.pid %peek path]
- :- ~[effect]
- state(peek-requests (~(put by peek-requests.state) u.pid %balance))
- ::
- ++ do-update-block
- |= =cause
- ?> ?=(%update-block -.cause)
- %- (debug "update-block")
- %- (debug "last block: {<last-block.state>}")
- =/ pid=(unit @ud) (generate-pid:v %block)
- ?~ pid `state
- =/ =path /heavy
- =/ =effect [%npc u.pid %peek path]
- :- ~[effect]
- state(peek-requests (~(put by peek-requests.state) u.pid %block))
- ::
- ++ do-import-keys
- |= =cause
- ?> ?=(%import-keys -.cause)
- =/ new-keys=_keys.state
- %+ roll keys.cause
- |= [[=trek =meta] acc=_keys.state]
- (~(put of acc) trek meta)
- =/ master-key=coil
- %- head
- %+ murn ~(tap of new-keys)
- |= [t=trek m=meta]
- ^- (unit coil)
- ?: ?&
- ?=(%coil -.m)
- =((slag 2 t) /pub/m)
- ==
- `m
- ~
- =/ key-list=(list tape)
- %+ murn ~(tap of new-keys)
- |= [t=trek m=meta]
- ^- (unit tape)
- ?: ?=(%coil -.m)
- `(en:base58:wrap p.key.m)
- ~
- =. master.state `master-key
- :_ state(keys new-keys)
- :~ :- %markdown
- %- crip
- """
- ## imported keys
- {<key-list>}
- """
- [%exit 0]
- ==
- ::
- ++ do-export-keys
- |= =cause
- ?> ?=(%export-keys -.cause)
- =/ keys-list=(list [trek meta])
- ~(tap of keys.state)
- =/ dat-jam (jam keys-list)
- =/ path=@t 'keys.export'
- =/ =effect [%file %write path dat-jam]
- :_ state
- :~ effect
- :- %markdown
- %- crip
- """
- ## exported keys
- - path: {<path>}
- """
- [%exit 0]
- ==
- ::
- ++ do-export-master-pubkey
- |= =cause
- ?> ?=(%export-master-pubkey -.cause)
- %- (debug "import-master-pubkey")
- ?~ master.state
- ~& "wallet warning: no master keys available for export"
- [[%exit 0]~ state]
- =/ master-coil=coil ~(master get:v %pub)
- ?. ?=(%pub -.key.master-coil)
- ~& "wallet fatal: master pubkey malformed"
- [[%exit 0]~ state]
- =/ dat-jam=@ (jam master-coil)
- =/ key-b58=tape (en:base58:wrap p.key.master-coil)
- =/ cc-b58=tape (en:base58:wrap cc.master-coil)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## exported master public key:
- - pubkey: {key-b58}
- - chaincode: {cc-b58}
- """
- [%exit 0]
- [%file %write 'master-pubkey.export' dat-jam]
- ==
- ::
- ++ do-import-master-pubkey
- |= =cause
- ?> ?=(%import-master-pubkey -.cause)
- %- (debug "import-master-pubkey: {<coil.cause>}")
- =/ master-pubkey-coil=coil coil.cause
- =. master.state (some master-pubkey-coil)
- =/ label `(crip "master-public-{<(end [3 4] p.key.master-pubkey-coil)>}")
- =. keys.state (key:put:v master-pubkey-coil ~ label)
- =/ key-b58=tape (en:base58:wrap p.key.master-pubkey-coil)
- =/ cc-b58=tape (en:base58:wrap cc.master-pubkey-coil)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## imported master public key:
- - pubkey: {key-b58}
- - chaincode: {cc-b58}
- """
- [%exit 0]
- ==
- ::
- ++ do-gen-master-privkey
- |= =cause
- ?> ?=(%gen-master-privkey -.cause)
- :: We do not need to reverse the endian-ness of the seedphrase
- :: because the bip39 code expects a tape.
- =/ seed=byts [64 (to-seed:bip39 (trip seedphrase.cause) "")]
- =/ cor (from-seed:s10 seed)
- =/ master-pubkey-coil=coil [%coil [%pub public-key] chain-code]:cor
- =/ master-privkey-coil=coil [%coil [%prv private-key] chain-code]:cor
- =. master.state (some master-pubkey-coil)
- =/ public-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
- =/ private-label `(crip "master-private-{<(end [3 4] public-key:cor)>}")
- =. keys.state (key:put:v master-privkey-coil ~ private-label)
- =. keys.state (key:put:v master-pubkey-coil ~ public-label)
- =. keys.state (seed:put:v seedphrase.cause)
- %- (debug "master.state: {<master.state>}")
- [[%exit 0]~ state]
- ::
- ++ do-gen-master-pubkey
- |= =cause
- ?> ?=(%gen-master-pubkey -.cause)
- =/ cor (from-private:s10 master-privkey.cause)
- =/ master-pubkey-coil=coil [%coil [%pub public-key] chain-code]:cor
- =/ master-privkey-coil=coil [%coil [%prv private-key] chain-code]:cor
- %- (debug "Generated master public key: {<public-key:cor>}")
- =/ public-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
- =/ private-label `(crip "master-private-{<(end [3 4] public-key:cor)>}")
- =. master.state (some master-pubkey-coil)
- =. keys.state (key:put:v master-privkey-coil ~ private-label)
- =. keys.state (key:put:v master-pubkey-coil ~ public-label)
- %- (debug "master.state: {<master.state>}")
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## master public key
- {<public-key:cor>}
- """
- [%exit 0]
- ==
- ::
- ++ do-make-tx
- |= =cause
- ?> ?=(%make-tx -.cause)
- %- (debug "make-tx: creating raw-tx")
- :: note that new:raw-tx calls +validate already
- =/ raw=raw-tx:transact (new:raw-tx:transact p.dat.cause)
- =/ tx-id id.raw
- =/ nock-cause=$>(%fact cause:dumb)
- [%fact %0 %heard-tx raw]
- %- (debug "make-tx: made raw-tx, poking over npc")
- :_ state
- :~
- [%npc 0 %poke nock-cause]
- [%exit 0]
- ==
- ::
- ++ do-list-pubkeys
- |= =cause
- ?> ?=(%list-pubkeys -.cause)
- =/ pubkeys ~(coils get:v %pub)
- =/ base58-keys=(list tape)
- %+ turn pubkeys
- |= =coil
- =/ pubkey=schnorr-pubkey:transact pub:(from-public:s10 [p.key cc]:coil)
- %- trip
- (to-b58:schnorr-pubkey:transact pubkey)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## pubkeys
- {<base58-keys>}
- """
- [%exit 0]
- ==
- ::
- ++ do-show-seedphrase
- |= =cause
- ?> ?=(%show-seedphrase -.cause)
- %- (debug "show-seedphrase")
- =/ =meta seed:get:v
- =/ seedphrase=@t
- ?: ?=(%seed -.meta)
- +.meta
- %- crip
- "no seedphrase found"
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## seedphrase
- {<seedphrase>}
- """
- [%exit 0]
- ==
- ::
- ++ do-show-master-pubkey
- |= =cause
- ?> ?=(%show-master-pubkey -.cause)
- %- (debug "show-master-pubkey")
- =/ =meta ~(master get:v %pub)
- ?> ?=(%coil -.meta)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## master public key
- {(en:base58:wrap p.key.meta)}
- """
- [%exit 0]
- ==
- ::
- ++ do-show-master-privkey
- |= =cause
- ?> ?=(%show-master-privkey -.cause)
- %- (debug "show-master-privkey")
- =/ =meta ~(master get:v %prv)
- ?> ?=(%coil -.meta)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## master private key
- {(en:base58:wrap p.key.meta)}
- """
- [%exit 0]
- ==
- ++ do-scan
- |= =cause
- ?> ?=(%scan -.cause)
- %- (debug "scan: scanning {<search-depth.cause>} addresses")
- ?> ?=(^ master.state)
- :: get all public keys up to search depth
- =/ index=@ud search-depth.cause
- =/ coils=(list coil)
- =/ keys=(list coil) [~(master get:v %pub)]~
- =| done=_|
- |- ^- (list coil)
- ?: done keys
- =? done =(0 index) &
- =/ base=trek /keys/pub/[ux/p.key:(public:master master.state)]/[ud/index]
- =/ key=(unit coil)
- ;; (unit coil)
- (~(get of keys.state) base)
- %= $
- index ?:(=(0 index) 0 (dec index))
- keys ?^(key (snoc keys u.key) keys)
- ==
- :: fail when no coils
- ?: ?=(~ coils)
- ~|("no coils for master key" !!)
- :: generate first names of notes owned by each pubkey
- =/ first-names=(list [hash:transact schnorr-pubkey:transact])
- %+ turn coils
- |= =coil
- :: create lock from public key
- =/ pubkey=schnorr-pubkey:transact pub:(from-public:s10 [p.key cc]:coil)
- =/ =lock:transact (new:lock:transact pubkey)
- :: generate name and take first name
- =/ match-name=nname:transact
- %- new:nname:transact
- :* lock
- [*hash:transact %.n] :: empty source, not a coinbase
- *timelock:transact :: no timelock
- ==
- [-.match-name pubkey]
- :: find notes with matching first names in balance
- =/ notes=(list nnote:transact)
- %+ murn
- ~(tap z-by:zo balance.state)
- |= [name=nname:transact note=nnote:transact]
- ^- (unit nnote:transact)
- :: check if first name matches any in our list
- =/ matches
- %+ lien first-names
- |= [first-name=hash:transact pubkey=schnorr-pubkey:transact]
- =/ =lock:transact (new:lock:transact pubkey)
- :: update lock if include-multisig is true and pubkey is in
- :: the multisig set in the note's lock
- =? lock
- ?& include-multisig.cause
- (~(has z-in:zo pubkeys.lock.note) pubkey)
- ==
- lock.note
- :: update match-name if include-timelocks is set
- =? first-name include-timelocks.cause
- =< -
- %- new:nname:transact
- :* lock
- [*hash:transact %.n] :: empty source, not a coinbase
- timelock.note :: include timelock
- ==
- =(-.name first-name)
- ?:(matches `note ~)
- %- (debug "found matches: {<notes>}")
- =/ nodes=markdown:m
- :~ :- %leaf
- :- %heading
- :* %atx 1
- :~ [%text 'Scan Result']
- ==
- ==
- :- %container
- :- %ul
- :* 0 '*'
- (turn notes display-note)
- ==
- ==
- :_ state
- ~[(make-markdown-effect nodes) [%exit 0]]
- ::
- ++ do-list-notes
- |= =cause
- ?> ?=(%list-notes -.cause)
- %- (debug "list-notes")
- =/ notes=(list [name=nname:transact note=nnote:transact])
- %+ sort ~(tap z-by:zo balance.state)
- |= $: a=[name=nname:transact note=nnote:transact]
- b=[name=nname:transact note=nnote:transact]
- ==
- (gth assets.note.a assets.note.b)
- =/ nodes=markdown:m
- %+ welp
- %- need
- %- de:md
- %- crip
- """
- ## wallet notes
- """
- %- zing
- %+ turn notes
- |= [* =nnote:transact]
- (display-note nnote)
- :_ state
- ~[(make-markdown-effect nodes) [%exit 0]]
- ::
- ++ do-list-notes-by-pubkey
- |= =cause
- ~& "list-notes-by-pubkey"
- ?> ?=(%list-notes-by-pubkey -.cause)
- ~& "list-notes-by-pubkey: {<pubkey.cause>}"
- =/ target-pubkey=schnorr-pubkey:transact
- (from-b58:schnorr-pubkey:transact pubkey.cause)
- =/ matching-notes=(list [name=nname:transact note=nnote:transact])
- %+ skim ~(tap z-by:zo balance.state)
- |= [name=nname:transact note=nnote:transact]
- (~(has z-in:zo pubkeys.lock.note) target-pubkey)
- =/ sorted-notes=(list [name=nname:transact note=nnote:transact])
- %+ sort matching-notes
- |= $: a=[name=nname:transact note=nnote:transact]
- b=[name=nname:transact note=nnote:transact]
- ==
- (gth assets.note.a assets.note.b)
- =/ nodes=markdown:m
- %+ welp
- %- need
- %- de:md
- %- crip
- """
- ## wallet notes for pubkey {<(to-b58:schnorr-pubkey:transact target-pubkey)>}
- """
- %- zing
- %+ turn sorted-notes
- |= [* =nnote:transact]
- (display-note nnote)
- :_ state
- ~[(make-markdown-effect nodes) [%raw sorted-notes] [%exit 0]]
- ::
- ++ do-simple-spend
- |= =cause
- ?> ?=(%simple-spend -.cause)
- %- (debug "simple-spend: {<names.cause>}")
- :: for now, each input corresponds to a single name and recipient. all
- :: assets associated with the name are transferred to the recipient.
- ::
- :: thus there is one recipient per name, and one seed per recipient.
- ::
- =/ names=(list nname:transact)
- %+ turn names.cause
- |= [first=@t last=@t]
- (from-b58:nname:transact [first last])
- =/ recipients=(list lock:transact)
- %+ turn recipients.cause
- |= [m=@ pks=(list @t)]
- %+ m-of-n:new:lock:transact m
- %- ~(gas z-in:zo *(z-set:zo schnorr-pubkey:transact))
- %+ turn pks
- |= pk=@t
- (from-b58:schnorr-pubkey:transact pk)
- ::
- =/ gifts=(list coins:transact) gifts.cause
- ::
- ?. ?& =((lent names) (lent recipients))
- =((lent names) (lent gifts))
- ==
- ~|("different number of names/recipients/gifts" !!)
- =| ledger=(list [name=nname:transact recipient=lock:transact gifts=coins:transact])
- =. ledger
- |-
- ?~ names ledger
- :: since we assert they are equal in length above, this is just to get
- :: the i face
- ?~ gifts ledger
- ?~ recipients ledger
- %= $
- ledger [[i.names i.recipients i.gifts] ledger]
- names t.names
- gifts t.gifts
- recipients t.recipients
- ==
- ::
- :: the fee is subtracted from the first note that permits doing so without overspending
- =/ fee=coins:transact fee.cause
- :: use the first private key to construct the subsequent inputs
- =/ sender=coil
- =/ private-keys=(list coil) ~(coils get:v %prv)
- ?~ private-keys
- ~|("No private keys available for signing" !!)
- (head private-keys)
- =/ sender-key=schnorr-seckey:transact
- (from-atom:schnorr-seckey:transact p.key.sender)
- :: for each name, create an input from the corresponding note in sender's
- :: balance at the current block. the fee will be subtracted entirely from
- :: the first note that has sufficient assets for both the fee and the gift.
- :: the refund is sent to receive-address.state
- =/ [ins=(list input:transact) spent-fee=?]
- %^ spin ledger `?`%.n
- |= $: $: name=nname:transact
- recipient=lock:transact
- gift=coins:transact
- ==
- spent-fee=?
- ==
- =/ note=nnote:transact (get-note:v name)
- ?: (gth gift assets.note)
- ~| "gift {<gift>} larger than assets {<assets.note>} for recipient {<recipient>}"
- !!
- ?: ?& !spent-fee
- (lte (add gift fee) assets.note)
- ==
- :: we can subtract the fee from this note
- :_ %.y
- %- with-choice:with-refund:simple-from-note:new:input:transact
- [recipient gift fee note sender-key receive-address.state]
- :: :: we cannot subtract the fee from this note, or we already have from a previous one
- :_ %.n
- %- with-choice:with-refund:simple-from-note:new:input:transact
- [recipient gift 0 note sender-key receive-address.state]
- ::
- ?. spent-fee
- ~|("no note suitable to subtract fee from, aborting operation" !!)
- =/ ins-draft=inputs:transact (multi:new:inputs:transact ins)
- ?: ?=(~ last-block.state)
- ~|("last-block unknown!" !!)
- :: name is the b58-encoded name of the first input
- =/ draft-name=@t
- %- head
- %+ turn ~(tap z-by:zo (names:inputs:transact ins-draft))
- |= =nname:transact
- =< last
- (to-b58:nname:transact nname)
- :: jam inputs and save as draft
- =/ =draft
- %* . *draft
- p ins-draft
- name draft-name
- ==
- =/ draft-jam (jam draft)
- =/ path=@t
- %- crip
- "./drafts/{(trip name.draft)}.draft"
- %- (debug "saving draft to {<path>}")
- =/ =effect [%file %write path draft-jam]
- :- ~[effect [%exit 0]]
- state
- ::
- ++ do-keygen
- |= =cause
- ?> ?=(%keygen -.cause)
- =+ [seed-phrase=@t cor]=(gen-master-key:s10 entropy.cause salt.cause)
- =/ master-public-coil [%coil [%pub public-key] chain-code]:cor
- =/ master-private-coil [%coil [%prv private-key] chain-code]:cor
- =. master.state (some master-public-coil)
- %- (debug "keygen: public key: {<(en:base58:wrap public-key:cor)>}")
- %- (debug "keygen: private key: {<(en:base58:wrap private-key:cor)>}")
- =/ pub-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
- =/ prv-label `(crip "master-public-{<(end [3 4] public-key:cor)>}")
- =. keys.state (key:put:v master-public-coil ~ pub-label)
- =. keys.state (key:put:v master-private-coil ~ prv-label)
- =. keys.state (seed:put:v seed-phrase)
- :_ state
- :~ :- %markdown
- %- crip
- """
- ## Keygen
- ### New Public Key
- {<(en:base58:wrap public-key:cor)>}
- ### New Private Key
- {<(en:base58:wrap private-key:cor)>}
- ### Chain Code
- {<(en:base58:wrap chain-code:cor)>}
- ### Seed Phrase
- {<seed-phrase>}
- """
- [%exit 0]
- ==
- ::
- :: derives child %pub or %prv key of current master key
- :: at index `i`. this will overwrite existing paths.
- ++ do-derive-child
- |= =cause
- ?> ?=(%derive-child -.cause)
- =/ par=coil ~(master get:v key-type.cause)
- =/ child=coil (derive-child:v par i.cause)
- =. keys.state
- (key:put:v child `i.cause label.cause)
- :- [%exit 0]~
- state
- ::
- ++ do-sign-tx
- |= =cause
- ?> ?=(%sign-tx -.cause)
- %- (debug "sign-tx: {<dat.cause>}")
- :: get private key at specified index, or first derived key if no index
- =/ private-keys=(list coil) ~(coils get:v %prv)
- ?~ private-keys
- ~|("No private keys available for signing" !!)
- =/ sender=coil
- ?~ index.cause i.private-keys
- =/ key-at-index=meta (~(by-index get:v %prv) u.index.cause)
- ?> ?=(%coil -.key-at-index)
- key-at-index
- =/ sender-key=schnorr-seckey:transact
- (from-atom:schnorr-seckey:transact p.key.sender)
- =/ signed-inputs=inputs:transact
- %- ~(run z-by:zo p.dat.cause)
- |= =input:transact
- %- (debug "signing input: {<input>}")
- =. spend.input
- %+ sign:spend:transact
- spend.input
- sender-key
- input
- =/ signed-draft=draft
- %= dat.cause
- p signed-inputs
- ==
- =/ draft-jam (jam signed-draft)
- =/ path=@t
- %- crip
- "./drafts/{(trip name.signed-draft)}.draft"
- %- (debug "saving input draft to {<path>}")
- =/ =effect [%file %write path draft-jam]
- :- ~[effect [%exit 0]]
- state
- ::
- ++ do-advanced-spend-seed
- |= cause=advanced-spend-seed
- ^- [(list effect) ^state]
- |^
- =? active-draft.state ?=(~ active-draft.state) `*draft-name
- =? active-seed.state ?=(~ active-seed.state) `*seed-name
- ?- -.cause
- %new do-new
- %set-name do-set-name
- %set-source do-set-source
- %set-recipient do-set-recipient
- %set-timelock do-set-timelock
- %set-gift do-set-gift
- %set-parent-hash do-set-parent-hash
- %set-parent-hash-from-name do-set-parent-hash-from-name
- %print-status do-print-status
- ==
- ::
- ++ do-new
- ?> ?=([%new *] cause)
- =/ sed=preseed
- %* . *preseed
- name name.cause
- ==
- (write-seed sed)
- ::
- ++ do-set-name
- ?> ?=([%set-name *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =. name.u.pre new-name.cause
- (write-seed u.pre)
- ::
- ++ do-set-source
- ?> ?=([%set-source *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ sed=preseed
- ?~ source.cause
- %= u.pre
- output-source.p ~
- output-source.q %.y
- ==
- %= u.pre
- output-source.p (some (from-b58:source:transact u.source.cause))
- output-source.q %.y
- ==
- (write-seed sed)
- ::
- ++ do-set-recipient
- ?> ?=([%set-recipient *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ recipient=lock:transact
- %+ m-of-n:new:lock:transact m.recipient.cause
- %- ~(gas z-in:zo *(z-set:zo schnorr-pubkey:transact))
- (turn pks.recipient.cause from-b58:schnorr-pubkey:transact)
- =/ sed=preseed
- %= u.pre
- recipient.p recipient
- recipient.q %.y
- ==
- (write-seed sed)
- ::
- ++ do-set-timelock
- ?> ?=([%set-timelock *] cause)
- ::TODO
- !!
- ::
- ++ do-set-gift
- ?> ?=([%set-gift *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ sed=preseed
- %= u.pre
- gift.p gift.cause
- gift.q %.y
- ==
- (write-seed sed)
- ::
- ++ do-set-parent-hash
- ?> ?=([%set-parent-hash *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ sed=preseed
- %= u.pre
- parent-hash.q %.y
- parent-hash.p (from-b58:hash:transact parent-hash.cause)
- ==
- (write-seed sed)
- ::
- ++ do-set-parent-hash-from-name
- ?> ?=([%set-parent-hash-from-name *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ name=nname:transact (from-b58:nname:transact name.cause)
- =/ not=nnote:transact (get-note:v name)
- =/ sed=preseed
- %= u.pre
- parent-hash.p (hash:nnote:transact not)
- parent-hash.q %.y
- ==
- (write-seed sed)
- ::
- ++ do-print-status
- ?> ?=([%print-status *] cause)
- =/ pre=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ pre)
- =/ output-source-text=tape
- ?: !output-source.q.u.pre
- "Unset (any output source is OK)"
- <output-source.p.u.pre>
- =/ recipient-text=tape
- ?: !recipient.q.u.pre
- "Unset"
- <recipient.p.u.pre>
- =/ timelock-text=tape
- ?: !timelock-intent.q.u.pre
- "Unset (no intent)"
- <timelock-intent.p.u.pre>
- =/ gift-text=tape
- ?: !gift.q.u.pre
- "Gift: unset (gift must be nonzero)"
- ?: =(0 gift.p.u.pre)
- """
- Gift: 0 (must be nonzero)
- """
- """
- Gift: {<gift.p.u.pre>}
- """
- =/ status-text=tape
- """
- ## Seed Status
- ### Output Source
- {output-source-text}
- ### Recipient
- {recipient-text}
- ### Timelock Intent
- {timelock-text}
- ### Gift
- {gift-text}
- """
- :_ state
- (print (need (de:md (crip status-text))))
- ::
- ++ write-seed
- |= sed=preseed
- ^- [(list effect) ^state]
- =. active-seed.state (some name.sed)
- =. draft-tree.state (~(add-seed plan draft-tree.state) name.sed sed)
- =^ writes state write-draft:d
- [writes state]
- -- ::+do-advanced-spend-seed
- ::
- ++ do-advanced-spend-input
- |= cause=advanced-spend-input
- ^- [(list effect) ^state]
- |^
- ?- -.cause
- %new do-new
- %set-name do-set-name
- %add-seed do-add-seed
- %set-fee do-set-fee
- %set-note-from-name do-set-note-from-name
- %set-note-from-hash do-set-note-from-hash
- %derive-note-from-seeds do-derive-note-from-seeds
- %remove-seed do-remove-seed
- %remove-seed-by-hash do-remove-seed-by-hash
- %print-status do-print-status
- ==
- ::
- ++ do-new
- ?> ?=([%new *] cause)
- =/ inp=preinput
- %* . *preinput
- name name.cause
- ==
- =. active-input.state (some name.cause)
- (write-input inp)
- ::
- ++ do-set-name
- ?> ?=([%set-name *] cause)
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =. name.u.pre new-name.cause
- =. active-input.state (some new-name.cause)
- (write-input u.pre)
- ::
- ++ do-add-seed
- ?> ?=([%add-seed *] cause)
- ::
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =/ sed=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ sed)
- ?: (~(has z-in:zo seeds.spend.p.u.pre) p.u.sed)
- :_ state
- =/ nodes=markdown:m
- :~ :- %leaf
- :- %paragraph
- :~ [%text (crip "seed already exists in .spend, doing nothing.")]
- ==
- ==
- (print nodes)
- =/ inp=preinput
- %= u.pre
- seeds.spend.p (~(put z-in:zo seeds.spend.p.u.pre) p.u.sed)
- seeds.spend.q %.y
- ==
- (write-input inp)
- ::
- ++ do-set-fee
- ?> ?=([%set-fee *] cause)
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =. fee.spend.p.u.pre fee.cause
- =. fee.spend.q.u.pre %.y
- (write-input u.pre)
- ::
- ++ do-set-note-from-name
- ?> ?=([%set-note-from-name *] cause)
- ::
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =/ name=nname:transact (from-b58:nname:transact name.cause)
- =/ not=nnote:transact (get-note:v name)
- =/ inp=preinput
- %= u.pre
- note.p not
- note.q %.y
- ==
- (write-input inp)
- ::
- ++ do-set-note-from-hash
- ?> ?=([%set-note-from-hash *] cause)
- ::
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =/ =hash:transact (from-b58:hash:transact hash.cause)
- =/ note=nnote:transact (get-note-from-hash:v hash)
- =/ inp=preinput
- %= u.pre
- note.p note
- note.q %.y
- ==
- (write-input inp)
- ::
- ++ do-derive-note-from-seeds
- ?> ?=([%derive-note-from-seeds *] cause)
- ::
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =/ seeds-list=(list seed:transact)
- ~(tap z-in:zo seeds.spend.p.u.pre)
- ?~ seeds-list
- :_ state
- =/ nodes=markdown:m
- :~ :- %leaf
- :- %paragraph
- :~ [%text (crip "no seeds exist in .spend, so note cannot be set.")]
- ==
- ==
- (print nodes)
- =/ =hash:transact parent-hash.i.seeds-list
- =/ note=nnote:transact (get-note-from-hash:v hash)
- =/ inp=preinput
- %= u.pre
- note.p note
- note.q %.y
- ==
- (write-input inp)
- ::
- ++ do-remove-seed
- ?> ?=([%remove-seed *] cause)
- ::
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =. active-input.state (some input-name.cause)
- =/ sed=(unit preseed)
- (get-seed:p seed-name.cause)
- ?> ?=(^ sed)
- ?: !(~(has z-in:zo seeds.spend.p.u.pre) p.u.sed)
- :_ state
- =/ nodes=markdown:m
- :~ :- %leaf
- :- %paragraph
- :~ [%text (crip "seed does not exist in .spend, doing nothing")]
- ==
- ==
- (print nodes)
- =/ inp=preinput
- %= u.pre
- seeds.spend.p (~(del z-in:zo seeds.spend.p.u.pre) p.u.sed)
- seeds.spend.q !=(*seeds:transact seeds.spend.p.u.pre)
- ==
- (write-input inp)
- ::
- ++ do-remove-seed-by-hash
- ?> ?=([%remove-seed-by-hash *] cause)
- :: find seed with hash
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =. active-input.state (some input-name.cause)
- =/ seed-hashes=(z-map:zo hash:transact seed:transact)
- %- ~(gas z-by:zo *(z-map:zo hash:transact seed:transact))
- %+ turn ~(tap z-in:zo seeds.spend.p.u.pre)
- |= sed=seed:transact
- [(hash:seed:transact sed) sed]
- =/ has=hash:transact (from-b58:hash:transact hash.cause)
- ?. (~(has z-by:zo seed-hashes) has)
- :_ state
- =/ nodes=markdown:m
- :~ :- %leaf
- :- %paragraph
- :~ [%text (crip "seed does not exist in .spend, doing nothing")]
- ==
- ==
- (print nodes)
- =/ remove-seed=seed:transact
- (~(got z-by:zo seed-hashes) has)
- ::
- =/ inp=preinput
- %= u.pre
- seeds.spend.p (~(del z-in:zo seeds.spend.p.u.pre) remove-seed)
- seeds.spend.q !=(*seeds:transact seeds.spend.p.u.pre)
- ==
- (write-input inp)
- ::
- ++ do-print-status
- ?> ?=([%print-status *] cause)
- =/ pre=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ pre)
- =| status-nodes=markdown:m
- =. status-nodes
- %+ snoc status-nodes
- :- %leaf
- :- %paragraph
- ?: !signature.spend.q.u.pre
- :: TODO we removed the ability to sign in this control flow
- [%text (crip ".signature: unset")]~
- :: check the signature
- ::
- :: get a .parent-hash of a seed. they have to all be the same, so which
- :: one doesn't matter; if they're not all the same validation will fail.
- =/ seeds-list=(list seed:transact)
- ~(tap z-in:zo seeds.spend.p.u.pre)
- ?~ seeds-list
- [%text (crip "no seeds exist, so signature cannot be checked")]~
- =/ parent-note-hash=hash:transact parent-hash.i.seeds-list
- =/ parent-note-hash-b58=tape
- (trip (to-b58:hash:transact parent-note-hash))
- =/ parent-note-name=(unit nname:transact)
- (~(get z-by:zo hash-to-name.state) parent-note-hash)
- ?~ parent-note-name
- :~
- :- %text
- %- crip
- """
- note with hash {parent-note-hash-b58} present in .spend but
- has no matching .name in wallet
- """
- :- %text
- :: TODO better, more succint error message.
- '''
- this implies that it is not in the balance unless there is a hash collision.
- please report this as a bug if you are sure you have the $note, as this
- situation is very unlkely. the spend ought to still be valid in that case
- and you can broadcast it anyway.
- '''
- ==
- =/ parent-note-name-b58=tape
- =; [first=@t last=@t]
- "<(trip first)> <(trip last)>"
- (to-b58:nname:transact u.parent-note-name)
- =/ parent-note=(unit nnote:transact)
- (~(get z-by:zo balance.state) u.parent-note-name)
- ?~ parent-note
- :~
- :- %text
- %- crip
- """
- note with name {parent-note-name-b58} and hash {parent-note-hash-b58}
- present in .spend but not in balance
- """
- ==
- ?: (verify:spend:transact spend.p.u.pre u.parent-note)
- [%text (crip "signature(s) on spend are valid.")]~
- :: missing or invalid sigs
- =/ have-sigs
- %+ turn
- %~ tap z-in:zo
- ^- (z-set:zo schnorr-pubkey:transact)
- %~ key z-by:zo (need signature.spend.p.u.pre)
- |= pk=schnorr-pubkey:transact
- [%text (to-b58:schnorr-pubkey:transact pk)]
- ?~ have-sigs
- [%text 'no signatures found!']~
- =/ lock-b58=[m=@ pks=(list @t)]
- (to-b58:lock:transact recipient.i.seeds-list)
- =/ need-sigs
- (turn pks.lock-b58 (lead %text))
- ?~ need-sigs
- [%text 'no recipients found!']~
- ;: welp
- :~ [%text (crip "signature on spend did not validate.")]
- [%text (crip "signatures on spend:")]
- ==
- ::TODO check if any particular signature did not validate
- have-sigs
- :~ [%text (crip ".lock on parent note:")]
- [%text (crip "number of sigs required: {(scow %ud m.lock-b58)}")]
- [%text (crip "pubkeys of possible signers:")]
- ==
- need-sigs
- ==
- ::TODO check individual seeds? this would require some refactoring and
- ::the happy path does not involve adding unfinished seeds to an input.
- :_ state
- (print status-nodes)
- ::
- ++ write-input
- |= inp=preinput
- ^- [(list effect) ^state]
- =. active-input.state (some name.inp)
- =. draft-tree.state (~(add-input plan draft-tree.state) name.inp inp)
- =^ writes state write-draft:d
- [writes state]
- -- ::+do-advanced-spend-input
- ::
- ++ do-advanced-spend-draft
- |= cause=advanced-spend-draft
- ^- [(list effect) ^state]
- |^
- =? active-draft.state ?=(~ active-draft.state) `*draft-name
- ?- -.cause
- %new do-new
- %set-name do-set-name
- %add-input do-add-input
- %remove-input do-remove-input
- %remove-input-by-name do-remove-input-by-name
- %print-status do-print-status
- ==
- ::
- ++ do-new
- ?> ?=([%new *] cause)
- =. active-draft.state (some name.cause)
- =/ dat=draft
- %* . *draft
- name name.cause
- ==
- (write-draft dat)
- ::
- ++ do-set-name
- ?> ?=([%set-name *] cause)
- =/ pre=(unit draft)
- (get-draft:p draft-name.cause)
- ?> ?=(^ pre)
- =. active-draft.state (some new-name.cause)
- =. name.u.pre new-name.cause
- (write-draft u.pre)
- ::
- ++ do-add-input
- ?> ?=([%add-input *] cause)
- =/ pre=(unit draft)
- (get-draft:p draft-name.cause)
- ?> ?=(^ pre)
- =. active-draft.state (some draft-name.cause)
- =/ inp=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ inp)
- ?: (~(has z-by:zo p.u.pre) name.note.p.u.inp)
- :_ state
- %- print
- ^- markdown:m
- :_ ~ :- %leaf
- :- %paragraph
- :_ ~ :- %text
- %- crip
- """
- draft already has input with note name
- {<(to-b58:nname:transact name.note.p.u.inp)>}, doing nothing.
- """
- =. p.u.pre
- (~(put z-by:zo p.u.pre) [name.note.p.u.inp p.u.inp])
- (write-draft u.pre)
- ::
- ++ do-remove-input
- ?> ?=([%remove-input *] cause)
- =/ pre=(unit draft)
- (get-draft:p draft-name.cause)
- ?> ?=(^ pre)
- =. active-draft.state (some draft-name.cause)
- =/ inp=(unit preinput)
- (get-input:p input-name.cause)
- ?> ?=(^ inp)
- ?. (~(has z-by:zo p.u.pre) name.note.p.u.inp)
- :_ state
- %- print
- :_ ~ :- %leaf
- :- %paragraph
- :_ ~ :- %text
- %- crip
- """
- draft does not have input with note name
- {<(to-b58:nname:transact name.note.p.u.inp)>}, doing nothing.
- """
- ?. =(u.inp (~(got z-by:zo p.u.pre) name.note.p.u.inp))
- :_ state
- %- print
- :_ ~ :- %leaf
- :- %paragraph
- :_ ~ :- %text
- %- crip
- """
- draft has input with note name
- {<(to-b58:nname:transact name.note.p.u.inp)>}, but it is
- a different input. to remove this input, use %remove-input-by-name
- instead.
- """
- =. p.u.pre
- (~(del z-by:zo p.u.pre) name.note.p.u.inp)
- (write-draft u.pre)
- ::
- ++ do-remove-input-by-name
- ?> ?=([%remove-input-by-name *] cause)
- =/ pre=(unit draft)
- (get-draft:p draft-name.cause)
- =. active-draft.state (some draft-name.cause)
- ?> ?=(^ pre)
- =/ name=nname:transact (from-b58:nname:transact name.cause)
- ?. (~(has z-by:zo p.u.pre) name)
- :_ state
- %- print
- :_ ~ :- %leaf
- :- %paragraph
- :_ ~ :- %text
- %- crip
- """
- draft does not have input with note name {(trip first.name.cause)}
- {(trip last.name.cause)}, doing nothing.
- """
- =. p.u.pre (~(del z-by:zo p.u.pre) name)
- (write-draft u.pre)
- ::
- ++ do-print-status
- ?> ?=([%print-status *] cause)
- =/ pre=(unit draft)
- (get-draft:p draft-name.cause)
- =. active-draft.state (some draft-name.cause)
- ?> ?=(^ pre)
- =/ inputs=(list [name=nname:transact =input:transact])
- ~(tap z-by:zo p.u.pre)
- =/ input-texts=(list tape)
- %+ turn inputs
- |= [name=nname:transact =input:transact]
- =/ signature-text=tape ?~(signature.spend.input "unset" "set")
- =/ name-text=tape <(to-b58:nname:transact name)>
- =/ note-text=tape <(to-b58:hash:transact (hash:nnote:transact note.input))>
- =/ seeds-text=tape
- %- zing
- %+ turn ~(tap z-in:zo seeds.spend.input)
- |= =seed:transact
- """
- - recipient: {<(to-b58:lock:transact recipient.seed)>}
- - gift: {<gift.seed>}
- - parent hash: {<(to-b58:hash:transact parent-hash.seed)>}
- """
- """
- #### Input {name-text}:
- - Note hash: {note-text}
- - Fee: {<fee.spend.input>}
- - Signature: {signature-text}
- ##### Seeds
- {seeds-text}
- """
- =/ status-text=tape
- """
- ## Draft Status
- Name: {(trip name.u.pre)}
- Number of inputs: {<(lent inputs)>}
- ### Inputs
- {(zing input-texts)}
- """
- :_ state
- (print (need (de:md (crip status-text))))
- ::
- ++ write-draft
- |= dat=draft
- ^- [(list effect) ^state]
- =. active-draft.state (some name.dat)
- write-draft:d
- -- ::+do-advanced-spend-draft
- -- ::+poke
- --
|