CryptoNode is a bitcoin library written in Scala. It can be used for implementing nodes and wallets. It supports SPV mode out of the box and can be converted to a full node by implementing the core consensus rules and the execution engine. Note that this library is experimental so use it in TestNet only.
- Signing transactions and address generation:
- SegWit legacy (P2SH-P2WPKH)
- SegWit Bech32 (P2WPKH)
- P2SH-P2PK
- P2PKH
- Node connectivity:
- Listen for blocks and transactions
- Broadcast transaction
- SPV Mode:
- Merkle Blocks
- Bloom filters
- Local Bitcoind connectivity:
- RPC
- Blockchain database parser (to reuse bitcoind chain)
Please refer to the tests for example usage: https://github.com/scalahub/CryptoNode/tree/master/btc/src/test/scala/org/sh/cryptonode
From the TestBitcoinPeer example:
val useMainNet = false // set to true for main net (default)
val node = new org.sh.cryptonode.btc.BitcoinNode(useMainNet)
Once a node is created, here is how we can add handlers for listening to transactions and blocks.
node.addOnTxHandler("myTxHandler", tx => println(s"new transaction with id $tx"))
node.addOnBlkHandler("myBlkHandler", blk => println(s"new block with id $blk"))
The following code (based on the TestTx example) shows how how to create a transaction having both SegWit and non-SegWit inputs:
val useMainNet = false // mainnet is false
// generate a big integer to use as secret
val int = new ECCPrvKey("BB2AC60BC518C0E239D5AF9D8D051A6BDFD0D931268DCA70C59E5992", true) // random key
// three different types of private keys (can be generated from different ints but we use the same below)
val p2wpkh_key = new PrvKey_P2SH_P2WPKH(int.bigInt, useMainNet)
val p2sh_key = new PrvKey_P2SH_P2PK(int, useMainNet)
val p2pkh_key = new PrvKey_P2PKH(int, useMainNet)
assert(p2wpkh_key.pubKey.address == "2N6nA4btbY23GTQ4RJi3mMGTonzXN7dbphE") // (segwit)
assert(p2sh_key.pubKey.address == "2MwprvB9tUMtX4vK8zJK8K329fNu79CJgR7") // (p2sh)
assert(p2pkh_key.pubKey.address == "muLwLjVipwixXcECVMcEwgsrtfGRTB4zB1") // (p2pkh)
// We will create a transaction whose inputs are a mix of segwit, p2sh and p2pkh types. The inputs are below
val in0 = TxIn("0224c8875a43c482c81a508fafc10bd0f084b27b5543c228e48431985f321547", 0) // p2pkh
val in1 = TxIn("63bec90405a0c173ae928860a1e1d403e507ec225e2cdd07717c8408820d418b", 0) // segwit // 2031250 satoshis
val in2 = TxIn("db5a23f0da2502b8ef82d93453aa3be0b6e9a3ecfbfbc00818dc32b3c712d2d0", 0) // p2pkh
val in3 = TxIn("6d73e3c8f66869859dc5c1ba73f252b8db51950ebc1fc4a59dca3552a0085f9a", 0) // p2sh
val in4 = TxIn("6ce466eb0920f84cc2cfde1d359176c0baab7dc442e4e5763bf67e8fa96ee6a4", 0) // p2sh
val in5 = TxIn("b49f3d6d15f2bdd9217ba3caaf1bb1f2d9875c9657e6b0ac7a0ef841d486ad1d", 2) // p2pkh
val in6 = TxIn("b75dfb27477834b3060c8e956273bef81c62689a8b9ebb7cd4a8119508c2c798", 0) // segwit // 1015625 satoshis
// The transaction creates one output to 2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF, which is a faucet address and may
// be either P2PKH or P2SH (without seeing a spend, it is impossible to distinguish them)
// The following output was created
val out1 = TxOut(Some("2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF"), 32206092) // total amount is slightly more than 32206092
// Below are the steps to create the fully signed transaction
// Step 1: create an unsigned transaction
val tx0 = createTxRaw(Seq(in0,in1,in2,in3,in4,in5,in6), Seq(out1)) // unsigned tx
// Step 2: sign all the p2pkh inputs
val tx1 = p2pkh_key.signTx_P2PKH (tx0, Seq(0, 2, 5)) // inputs 0, 2, 5 are P2PKH
// Step 3: sign all the p2wpkh inputs. Note that SegWit signatures need the value in satoshis
val tx2 = p2wpkh_key.signTx(tx1, Seq((1, 2031250), (6, 1015625))) // inputs 1, 6 are segwit.
// Step 4: sign all p2sh_p2pk inputs
val signed = p2sh_key.signTx_P2SH_P2PK(tx2, Seq(3, 4)) // inputs 3, 4 are P2SH_P2PK
// above is a byte array encoding the fully signed raw Tx
val tx = new TxParser(signed).getTx // creates a Tx object
Once the transaction is created, we can broadcast it using the node created earlier:
node.pushTx(tx)
Thanks to Arubi from #bitcoin-dev (Freenode) for providing several test vectors.