shrub/lib/btc.hoon

475 lines
12 KiB
Plaintext
Raw Normal View History

2020-10-30 14:45:38 +03:00
/- sur=btc
^?
=< [sur .]
=, sur
2020-10-04 11:56:38 +03:00
|%
2020-10-29 15:10:54 +03:00
++ xpub-type
2020-10-31 12:41:00 +03:00
|= =xpub
2020-10-29 15:10:54 +03:00
^- bipt
2020-10-31 12:41:00 +03:00
=/ prefix=tape (scag 4 (trip xpub))
?: =("xpub" prefix) %44
?: =("ypub" prefix) %49
?: =("zpub" prefix) %84
2020-10-29 15:10:54 +03:00
~|("invalid xpub: {<xpub>}" !!)
2020-10-14 10:08:35 +03:00
:: big endian sha256: input and output are both MSB first (big endian)
2020-10-15 13:55:41 +03:00
::
2020-10-14 10:08:35 +03:00
++ sha256
|= =byts
^- hash256
2020-10-20 11:28:19 +03:00
%- hash256
%- flip:byt
[32 (shay (flip:byt byts))]
::
++ dsha256
|= =byts
(sha256 (sha256 byts))
::
2020-10-04 17:16:35 +03:00
++ hash-160
|= pubkey=@ux
^- hash160
=, ripemd:crypto
2020-10-20 11:28:19 +03:00
%- hash160
:- 20
2020-10-15 13:55:41 +03:00
%- ripemd-160
2020-10-14 10:08:35 +03:00
%- sha256 [(met 3 pubkey) pubkey]
2020-10-15 13:55:41 +03:00
::
2020-10-16 13:21:51 +03:00
++ to-script-pubkey
2020-12-09 22:59:13 +03:00
|= script=buffer ^- buffer
2020-10-16 13:21:51 +03:00
%- zing
:~ ~[0x19 0x76 0xa9 0x14]
2020-10-16 13:31:21 +03:00
script
2020-10-16 13:21:51 +03:00
~[0x88 0xac]
==
++ address-to-script-pubkey
2020-12-09 22:59:13 +03:00
|= =address ^- buffer
2020-10-19 11:36:57 +03:00
=/ hex=byts
?- -.address
%bech32
(to-hex:bech32 address)
%legacy
=/ h=@ux (@ux +.address)
[(met 3 h) h]
==
?. =(wid.hex 20)
2020-10-19 11:36:57 +03:00
~|("Only 20-byte addresses supported" !!)
2020-12-09 22:59:13 +03:00
(to-script-pubkey (from-byts:buf hex))
2020-12-13 23:33:08 +03:00
:: +txu: tx utility functions
++ txu
|%
++ en
|= data:tx
^- bytc
*bytc
++ de
|= b=bytc
^- data:tx
*data:tx
:: TODO parse out signatures
++ get-id
|= b=bytc
^- txid
(dsha256 b)
--
:: core to handle BIP174 PSBTs
2020-12-07 14:56:50 +03:00
::
++ pbt
2020-12-07 14:56:50 +03:00
|%
2020-12-09 20:33:50 +03:00
++ en
|%
++ globals
|= =rawtx
^- map:psbt
2020-12-10 16:22:48 +03:00
:~ [[1 0x0] rawtx]
==
::
++ input
|= i=in:psbt
^- map:psbt
2020-12-10 16:22:48 +03:00
:~ [[1 0x0] rawtx.i]
(witness-tx i)
(hdkey %input hdkey.i)
==
::
++ output
|= =out:psbt
^- map:psbt
2020-12-10 16:22:48 +03:00
?~ hk.out ~
:~ (hdkey %output u.hk.out)
==
::
++ witness-tx
|= i=in:psbt
^- keyval:psbt
:- [1 0x1]
%- cat:byt
2020-12-11 13:10:40 +03:00
:~ (flip:byt 8^value.utxo.i)
2020-12-10 16:22:48 +03:00
1^0x16
2^0x14
(hash-160 dat.pubkey.hdkey.i)
==
::
++ hdkey
2020-12-10 16:22:48 +03:00
|= [=target:psbt h=^hdkey]
^- keyval:psbt
=/ typ=@ux
?- target
%input 0x6
%output 0x2
==
2020-12-10 16:22:48 +03:00
:- (cat:byt ~[1^typ pubkey.h])
%- cat:byt
2020-12-10 12:14:03 +03:00
:~ fprint.h
%- to-byts:buf
2020-12-10 12:14:03 +03:00
:~ `@ux`bipt.h 0x0 0x0 0x80
0x0 0x0 0x0 0x80
0x0 0x0 0x0 0x80
2020-12-10 12:14:03 +03:00
`@ux`chyg.h 0x0 0x0 0x0
==
2020-12-11 13:10:40 +03:00
(flip:byt 4^idx.h)
==
2020-12-10 18:54:39 +03:00
::
++ keyval-byts
|= kv=keyval:psbt
^- bytc
%- cat:byt
:~ 1^wid.key.kv
key.kv
1^wid.val.kv
val.kv
==
::
++ map-byts
|= m=map:psbt
^- (unit bytc)
?~ m ~
:- ~
%- cat:byt
(turn m keyval-byts)
2020-12-10 16:22:48 +03:00
--
2020-12-11 13:10:40 +03:00
++ base64
|= b=bytc
^- base64:psbt
%- en:base64:mimes:html
(flip:byt b)
2020-12-09 20:33:50 +03:00
:: +encode: make base64 cord of PSBT
::
++ encode
2020-12-10 16:22:48 +03:00
|= [=rawtx =txid inputs=(list in:psbt) outputs=(list out:psbt)]
2020-12-10 19:15:25 +03:00
^- base64:psbt
2020-12-11 14:12:53 +03:00
=/ sep=(unit bytc) `1^0x0
2020-12-10 18:54:39 +03:00
=/ final=(list (unit bytc))
2020-12-11 14:12:53 +03:00
%+ join sep
2020-12-10 18:54:39 +03:00
%+ turn
%- zing
:~ ~[(globals:en rawtx)]
(turn inputs input:en)
(turn outputs output:en)
==
map-byts:en
2020-12-11 13:10:40 +03:00
%- base64:en
2020-12-10 19:15:25 +03:00
%- cat:byt
%+ weld ~[[5 0x70.7362.74ff]]
2020-12-11 14:12:53 +03:00
(murn (snoc final sep) same)
2020-12-08 20:31:21 +03:00
::
2020-12-08 15:47:51 +03:00
++ parse
|= psbt-base64=cord
^- (list map:psbt)
2020-12-09 22:59:13 +03:00
=/ todo=buffer
2020-12-08 17:48:40 +03:00
%+ slag 5 (to-buffer psbt-base64)
=| acc=(list map:psbt)
=| m=map:psbt
2020-12-08 17:48:40 +03:00
|-
?~ todo (snoc acc m)
:: 0x0: map separator
?: =(0x0 i.todo)
$(acc (snoc acc m), m *map:psbt, todo t.todo)
2020-12-08 17:48:40 +03:00
=+ [kv rest]=(next-keyval todo)
$(m (snoc m kv), todo rest)
:: +get-txid: extract txid from a valid PSBT
::
2020-12-08 15:32:23 +03:00
++ get-txid
2020-12-08 15:47:51 +03:00
|= psbt-base64=cord
2020-12-08 15:32:23 +03:00
^- txid
=/ tx=bytc
2020-12-07 14:56:50 +03:00
%- raw-tx
%+ slag 5
2020-12-08 15:47:51 +03:00
(to-buffer psbt-base64)
%- txid
%- flip:byt
%- sha256
(sha256 tx)
2020-12-07 14:56:50 +03:00
:: +raw-tx: extract hex transaction
:: looks for key 0x0 in global map
:: crashes if tx not in buffer
::
++ raw-tx
2020-12-09 22:59:13 +03:00
|= b=buffer
|- ^- bytc
2020-12-07 14:56:50 +03:00
?~ b !!
?: =(0x0 i.b) !!
2020-12-08 17:48:40 +03:00
=+ nk=(next-keyval b)
?: =(0x0 dat.key.kv.nk)
val.kv.nk
$(b rest.nk)
:: +next-keyval: returns next key-val in a PSBT map
2020-12-07 14:56:50 +03:00
:: input buffer head must be a map key length
::
2020-12-08 17:48:40 +03:00
++ next-keyval
2020-12-09 22:59:13 +03:00
|= b=buffer
^- [kv=keyval:psbt rest=buffer]
2020-12-07 14:56:50 +03:00
=+ klen=(snag 0 b)
=+ k=(swag [1 klen] b)
=+ vlen=(snag (add 1 klen) b)
=+ v=(swag [(add 2 klen) vlen] b)
=+ len=(add 2 (add klen vlen))
?> ?=([^ ^] [k v])
2020-12-08 15:42:39 +03:00
:_ (slag len b)
2020-12-09 22:59:13 +03:00
:- (to-byts:buf k)
(to-byts:buf v)
2020-12-08 15:42:39 +03:00
::
2020-12-08 15:36:51 +03:00
++ to-buffer
2020-12-08 15:47:51 +03:00
|= psbt-base64=cord
2020-12-09 22:59:13 +03:00
^- buffer
2020-12-08 15:36:51 +03:00
~| "Invalid PSBT"
2020-12-09 12:10:11 +03:00
=+ p=(de:base64:mimes:html psbt-base64)
2020-12-08 15:36:51 +03:00
?~ p !!
2020-12-11 13:10:40 +03:00
(from-byts:buf (flip:byt u.p))
2020-12-07 14:56:50 +03:00
--
2020-12-08 19:16:16 +03:00
:: buffer: byte buffer utilities
2020-10-16 10:33:10 +03:00
:: list of @ux that is big endian for hashing purposes
:: used to preserve 0s when concatenating byte sequences
::
2020-12-09 22:59:13 +03:00
++ buf
2020-10-15 13:55:41 +03:00
|%
2020-10-16 10:33:10 +03:00
++ from-byts
2020-12-09 22:59:13 +03:00
|= =byts ^- buffer
2020-10-16 10:33:10 +03:00
=/ b=(list @ux)
(flop (rip 3 dat.byts))
=/ pad=@ (sub wid.byts (lent b))
(weld (reap pad 0x0) b)
:: converts byts to a little endian buffer with wid length (trailing 0s)
:: atom 1 with wid=4 becomes ~[0x1 0x0 0x0 0x0]
:: 0xff11 with wid=8 becomes ~[0x11 0xff 0x0 0x0 0x0 0x0 0x0 0x0]
::
++ from-byts-le
|= =byts ^- buffer
=/ b=(list @ux) (rip 3 dat.byts)
=/ pad=@ (sub wid.byts (lent b))
(weld b (reap pad 0x0))
2020-10-16 10:33:10 +03:00
::
++ to-byts
2020-12-09 22:59:13 +03:00
|= b=buffer ^- byts
2020-10-16 10:33:10 +03:00
[(lent b) (rep 3 (flop b))]
2020-11-20 14:58:27 +03:00
::
++ concat-as-byts
2020-12-09 22:59:13 +03:00
|= bs=(list buffer) ^- byts
%- to-byts (zing bs)
2020-10-15 13:55:41 +03:00
--
2020-12-09 22:59:13 +03:00
++ byt
|%
:: +cat: concat byts, preserving MSB order
:: (cat:byt ~[4^0xaa00 4^0xbb00]) => [8 0xaa00.00bb.0000]
::
2020-12-09 22:59:13 +03:00
++ cat
|= bs=(list byts)
^- byts
:- (roll (turn bs |=(b=byts -.b)) add)
(can 3 (flop bs))
--
:: +flip:byt: flip endianness while preserving lead/trail zeroes
::
++ flip
|= b=byts
^- byts
2020-12-10 13:37:36 +03:00
[wid.b (rev 3 b)]
:: Converts a list of bits to a list of n-bit numbers
2020-10-05 10:49:09 +03:00
:: input-bits should be big-endian
2020-10-04 11:56:38 +03:00
::
2020-12-09 22:59:13 +03:00
++ bit
2020-10-05 10:49:09 +03:00
|%
:: rip atom a with num-bits. Preserve leading 0s, big endian
2020-10-16 10:33:10 +03:00
:: returns a list of bits
2020-10-05 10:49:09 +03:00
::
++ zeros-brip
|= [num-bits=@ a=@]
2020-10-05 10:49:09 +03:00
^- (list @)
=/ bits=(list @) (flop (rip 0 a))
=/ pad=@ (sub num-bits (lent bits))
(weld (reap pad 0) bits)
2020-12-09 12:10:11 +03:00
:: +convert: list of bits to a list of atoms each with bitwidth d(est)
2020-10-05 10:49:09 +03:00
::
++ convert
2020-10-15 13:55:41 +03:00
|= [d=@ bits=(list @)]
2020-10-05 10:49:09 +03:00
^- (list @)
=| ret=(list @)
|- ?~ bits ret
=/ dest-bits (scag d ((list @) bits))
:: left-shift the "missing" number of bits
=/ num=@
2020-12-09 12:10:11 +03:00
%+ lsh [0 (sub d (lent dest-bits))]
(rep 0 (flop dest-bits))
2020-10-05 10:49:09 +03:00
$(ret (snoc ret num), bits (slag d ((list @) bits)))
2020-10-15 13:55:41 +03:00
:: Converts e.g. ~[0 0 31 31 31 31 0 0] in base32 (5 bitwidth)
:: to ~[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
::
++ from-digits
|= [bitwidth=@ digits=(list @)]
^- (list @)
%- zing
%+ turn digits
|= d=@ (zeros-brip bitwidth d)
:: converts 40 bits: ~[0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0]
:: to 0x3fff.fc00 when cast to hex
::
++ to-atom
|= bits=(list @)
^- @
%+ rep 0
%- flop bits
2020-10-05 10:49:09 +03:00
--
2020-10-04 11:56:38 +03:00
::
2020-10-05 10:49:09 +03:00
++ bech32
|%
++ prefixes
^- (map network tape)
(my [[%main "bc"] [%testnet "tb"] ~])
++ charset "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+$ raw-decoded [hrp=tape data=(list @) checksum=(list @)]
2020-10-30 14:45:38 +03:00
:: below is a port of: https://github.com/bitcoinjs/bech32/blob/master/index.js
2020-10-05 10:49:09 +03:00
::
++ polymod
|= values=(list @)
|^ ^- @
=/ gen=(list @ux)
~[0x3b6a.57b2 0x2650.8e6d 0x1ea1.19fa 0x3d42.33dd 0x2a14.62b3]
=/ chk=@ 1
|- ?~ values chk
2020-12-09 12:10:11 +03:00
=/ top (rsh [0 25] chk)
2020-10-05 10:49:09 +03:00
=. chk
2020-12-09 12:10:11 +03:00
(mix i.values (lsh [0 5] (dis chk 0x1ff.ffff)))
2020-10-05 10:49:09 +03:00
$(values t.values, chk (update-chk chk top gen))
::
++ update-chk
|= [chk=@ top=@ gen=(list @ux)]
=/ is (gulf 0 4)
|- ?~ is chk
2020-12-09 12:10:11 +03:00
?: =(1 (dis 1 (rsh [0 i.is] top)))
2020-10-05 10:49:09 +03:00
$(is t.is, chk (mix chk (snag i.is gen)))
$(is t.is)
--
::
++ expand-hrp
|= hrp=tape
^- (list @)
2020-12-09 12:10:11 +03:00
=/ front (turn hrp |=(p=@tD (rsh [0 5] p)))
2020-10-05 10:49:09 +03:00
=/ back (turn hrp |=(p=@tD (dis 31 p)))
(zing ~[front ~[0] back])
::
++ verify-checksum
|= [hrp=tape data-and-checksum=(list @)]
^- ?
2020-10-05 10:55:10 +03:00
%+ |=([a=@ b=@] =(a b))
1
2020-10-05 10:49:09 +03:00
%- polymod
(weld (expand-hrp hrp) data-and-checksum)
::
++ checksum
|= [hrp=tape data=(list @)]
^- (list @)
:: xor 1 with the polymod
=/ pmod=@
%+ mix 1
%- polymod
(zing ~[(expand-hrp hrp) data (reap 6 0)])
%+ turn (gulf 0 5)
2020-12-09 12:10:11 +03:00
|=(i=@ (dis 31 (rsh [0 (mul 5 (sub 5 i))] pmod)))
2020-10-05 10:49:09 +03:00
::
++ charset-to-value
|= c=@tD
^- (unit @)
(find ~[c] charset)
++ value-to-charset
|= value=@
^- (unit @tD)
?: (gth value 31) ~
`(snag value charset)
::
++ is-valid
|= [bech=tape last-1-pos=@] ^- ?
?& ?|(=((cass bech) bech) =((cuss bech) bech)) :: to upper or to lower is same as bech
(gte last-1-pos 1)
(lte (add last-1-pos 7) (lent bech))
(lte (lent bech) 90)
(levy bech |=(c=@tD (gte c 33)))
(levy bech |=(c=@tD (lte c 126)))
==
:: data should be 5bit words
::
++ encode-raw
|= [hrp=tape data=(list @)]
2020-10-16 14:02:55 +03:00
^- bech32-address
2020-10-05 10:49:09 +03:00
=/ combined=(list @)
2020-10-16 14:02:55 +03:00
(weld data (checksum hrp data))
:- %bech32
%- crip
2020-10-05 10:49:09 +03:00
(zing ~[hrp "1" (tape (murn combined value-to-charset))])
++ decode-raw
2020-10-16 14:02:55 +03:00
|= b=bech32-address
2020-10-05 10:49:09 +03:00
^- (unit raw-decoded)
2020-10-16 14:02:55 +03:00
=/ bech (cass (trip +.b)) :: to lowercase
2020-10-05 10:49:09 +03:00
=/ pos (flop (fand "1" bech))
?~ pos ~
=/ last-1=@ i.pos
?. (is-valid bech last-1) :: check bech32 validity (not segwit validity or checksum)
~
=/ hrp (scag last-1 bech)
=/ encoded-data-and-checksum=(list @)
(slag +(last-1) bech)
=/ data-and-checksum=(list @)
%+ murn encoded-data-and-checksum
charset-to-value
?. =((lent encoded-data-and-checksum) (lent data-and-checksum)) :: ensure all were in CHARSET
~
?. (verify-checksum hrp data-and-checksum)
~
=/ checksum-pos (sub (lent data-and-checksum) 6)
`[hrp (scag checksum-pos data-and-checksum) (slag checksum-pos data-and-checksum)]
2020-10-15 16:08:29 +03:00
:: goes from a bech32 address to hex. Returns byts to preserve leading 0s
2020-10-15 13:55:41 +03:00
::
++ to-hex
2020-10-16 14:02:55 +03:00
|= b=bech32-address ^- hash
=/ d=(unit raw-decoded) (decode-raw b)
2020-10-15 13:55:41 +03:00
?~ d ~|("Invalid bech32 address" !!)
2020-10-15 16:08:29 +03:00
=/ bs=(list @)
2020-12-09 22:59:13 +03:00
(from-digits:bit 5 (slag 1 data.u.d))
2020-10-20 11:28:19 +03:00
=/ byt-len=@ (div (lent bs) 8)
2020-10-15 16:08:29 +03:00
?. =(0 (mod (lent bs) 8))
~|("Invalid bech32 address: not 8bit" !!)
2020-10-20 11:28:19 +03:00
?. ?|(?=(%20 byt-len) ?=(%32 byt-len))
~|("Invalid bech32 address: must be 20 (P2WPKH) or 32 (P2WSH) bytes" !!)
%- hash
2020-12-09 22:59:13 +03:00
[byt-len (to-atom:bit bs)]
2020-10-05 10:55:10 +03:00
:: pubkey is the 33 byte ECC compressed public key
2020-10-15 13:55:41 +03:00
::
2020-10-05 10:49:09 +03:00
++ encode-pubkey
|= [=network pubkey=@ux]
2020-10-16 14:02:55 +03:00
^- (unit bech32-address)
2020-10-05 10:55:10 +03:00
?. =(33 (met 3 pubkey))
~|('pubkey must be a 33 byte ECC compressed public key' !!)
2020-10-05 10:49:09 +03:00
=/ prefix (~(get by prefixes) network)
?~ prefix ~
:- ~
%+ encode-raw u.prefix
2020-12-09 22:59:13 +03:00
[0 (convert:bit 5 (zeros-brip:bit 160 dat:(hash-160 pubkey)))]
++ encode-hash-160
|= [=network h160=byts]
2020-10-16 14:02:55 +03:00
^- (unit bech32-address)
=/ prefix (~(get by prefixes) network)
?~ prefix ~
:- ~
%+ encode-raw u.prefix
2020-12-09 22:59:13 +03:00
[0 (convert:bit 5 (zeros-brip:bit 160 dat.h160))]
2020-10-05 10:49:09 +03:00
--
2020-10-04 11:56:38 +03:00
::
--