Repository: micro-bitcoin/uBitcoin Branch: master Commit: 877542fdc163 Files: 82 Total size: 639.6 KB Directory structure: gitextract_1v3_gt78/ ├── .gitignore ├── .mbedignore ├── LICENSE ├── README.md ├── examples/ │ ├── cpp/ │ │ ├── Makefile │ │ ├── README.md │ │ └── main.cpp │ ├── ecdh/ │ │ └── ecdh.ino │ ├── hash/ │ │ └── hash.ino │ ├── mnemonic/ │ │ └── mnemonic.ino │ ├── multisig/ │ │ └── multisig.ino │ ├── psbt/ │ │ └── psbt.ino │ ├── schnorr/ │ │ └── schnorr.ino │ ├── tx/ │ │ └── tx.ino │ └── xpubs/ │ └── xpubs.ino ├── keywords.txt ├── library.json ├── library.properties ├── src/ │ ├── BaseClasses.cpp │ ├── BaseClasses.h │ ├── Bitcoin.cpp │ ├── Bitcoin.h │ ├── BitcoinCurve.cpp │ ├── BitcoinCurve.h │ ├── Conversion.cpp │ ├── Conversion.h │ ├── Electrum.cpp │ ├── Electrum.h │ ├── HDWallet.cpp │ ├── Hash.cpp │ ├── Hash.h │ ├── Networks.cpp │ ├── Networks.h │ ├── OpCodes.h │ ├── PSBT.cpp │ ├── PSBT.h │ ├── Script.cpp │ ├── Transaction.cpp │ ├── uBitcoin_conf.h │ └── utility/ │ ├── segwit_addr.c │ ├── segwit_addr.h │ └── trezor/ │ ├── address.c │ ├── address.h │ ├── base58.c │ ├── base58.h │ ├── bignum.c │ ├── bignum.h │ ├── bip32.h │ ├── bip39.c │ ├── bip39.h │ ├── bip39_english.h │ ├── ecdsa.c │ ├── ecdsa.h │ ├── hasher.c │ ├── hasher.h │ ├── hmac.c │ ├── hmac.h │ ├── memzero.c │ ├── memzero.h │ ├── options.h │ ├── pbkdf2.c │ ├── pbkdf2.h │ ├── rand.c │ ├── rand.h │ ├── rfc6979.c │ ├── rfc6979.h │ ├── ripemd160.c │ ├── ripemd160.h │ ├── secp256k1.c │ ├── secp256k1.h │ ├── secp256k1.table │ ├── sha2.c │ ├── sha2.h │ ├── sha3.c │ └── sha3.h └── tests/ ├── Makefile ├── minunit.h ├── sysrand.c ├── test_conversion.cpp ├── test_hash.cpp ├── test_mnemonic.cpp └── test_schnorr.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode build ================================================ FILE: .mbedignore ================================================ examples/* tests/* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Stepan Snigirev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Micro-Bitcoin C++ Bitcoin library for 32-bit microcontrollers. The library supports [Arduino IDE](https://www.arduino.cc/), [ARM mbed](https://www.mbed.com/en/) and bare metal.
It provides a collection of convenient classes for Bitcoin: private and public keys, HD wallets, generation of the recovery phrases, PSBT transaction formats, scripts — everything required for a hardware wallet or other bitcoin-powered device. The library should work on any decent 32-bit microcontroller, like esp32, riscV, stm32 series and others. It *doesn't work* on 8-bit microcontrollers like a classic Arduino as these microcontrollers are not powerful enough to run complicated crypto algorithms. We use elliptic curve implementation from [trezor-crypto](https://github.com/trezor/trezor-firmware/tree/master/crypto). API is inspired by [Jimmy Song's](https://github.com/jimmysong/) Porgramming Blockchain class and the [book](https://github.com/jimmysong/programmingbitcoin). ## Documentation Check out our [tutorial](https://micro-bitcoin.github.io/#/tutorial/README) where we write a minimal hardware wallet, or browse the [API docs](https://micro-bitcoin.github.io/#/api/README). We also have a collection of [recepies](https://micro-bitcoin.github.io/#/recepies/README) for some common use-cases. Telegram group: https://t.me/arduinoBitcoin ## Alternative libraries [DIY Bitcoin Hardware website](https://diybitcoinhardware.com/) has a nice collection of bitcoin-related projects, resources and libraries for makers. A few bitcoin libraries: - [secp256k1](https://github.com/bitcoin-core/secp256k1) — elliptic curve library from Bitcoin Core and a [version](https://github.com/diybitcoinhardware/secp256k1-embedded) working with Arduino IDE & Mbed out of the box. - [libwally](https://github.com/ElementsProject/libwally-core/) - bitcoin library from Blockstream and a [version](https://github.com/diybitcoinhardware/libwally-embedded) working with Arduino IDE. - [f469-disco](https://github.com/diybitcoinhardware/f469-disco) - micropython bitcoin bundle for STM Discovery board and other platforms. ## Installation The library is [available](https://www.arduino.cc/reference/en/libraries/ubitcoin/) in the Arduino Library manager, or you can download and install it manually. [Download](https://github.com/micro-bitcoin/uBitcoin/archive/master.zip) the zip file from our [repository](https://github.com/micro-bitcoin/uBitcoin/) and select in Arduino IDE `Sketch` → `Include library` → `Add .ZIP library...`. Or clone it into your `Documents/Arduino/libraries` folder: ```sh git clone https://github.com/micro-bitcoin/uBitcoin.git ``` When installed you will also see a few examples in `File` → `Examples` → `Bitcoin` menu. ## Basic usage example First, don't forget to include necessary headers: ```cpp // we use these two in our sketch: #include "Bitcoin.h" #include "PSBT.h" // if using PSBT functionality // other headers of the library #include "Conversion.h" // to get access to functions like toHex() or fromBase64() #include "Hash.h" // if using hashes in your code ``` Now we can write a simple example that does the following: 1. Creates a master private key from a recovery phrase and empty password 2. Derives account and prints master public key for a watch-only wallet (`zpub` in this case) 3. Derives and print first segwit address 4. Parses, signs and prints signed PSBT transaction ```cpp // derive master private key HDPrivateKey hd("add good charge eagle walk culture book inherit fan nature seek repair", ""); // derive native segwit account (bip-84) for tesnet HDPrivateKey account = hd.derive("m/84'/1'/0'/"); // print xpub: vpub5YkPqVJTA7gjK...AH2rXvcAe3p781G Serial.println(account.xpub()); // or change the account type to UNKNOWN_TYPE to get tpub HDPublicKey xpub = account.xpub(); xpub.type = UNKNOWN_TYPE; // this time prints tpubDCnYy4Ty...dL4fLKsBFjFQwz Serial.println(xpub); // set back correct type to get segwit addresses by default xpub.type = P2WPKH; Serial.println(hd.fingerprint()); // print first address: tb1q6c8m3whsag5zadgl32nmhuf9q0qmtklws25n6g Serial.println(xpub.derive("m/0/0").address()); PSBT tx; // parse unsigned transaction tx.parseBase64("cHNidP8BAHECAAAAAUQS8FqBzYocPDpeQmXBRBH7NwZHVJF39dYJDCXxq" "zf6AAAAAAD+////AqCGAQAAAAAAFgAUuP0WcSBmiAZYi91nX90hg/cZJ1U8AgMAAAAAABYAF" "C1RhUR+m/nFyQkPSlP0xmZVxlOqAAAAAAABAR/gkwQAAAAAABYAFNYPuLrw6igutR+Kp7vxJ" "QPBtdvuIgYDzkBZaAkSIz0P0BexiPYfzInxu9mMeuaOQa1fGEUXcWIYoyAeuFQAAIABAACAA" "AAAgAAAAAAAAAAAAAAiAgMxjOiFQofq7l9q42nsLA3Ta4zKpEs5eCnAvMnQaVeqsBijIB64V" "AAAgAEAAIAAAACAAQAAAAAAAAAA"); // sign with the root key tx.sign(hd); // print signed transaction Serial.println(tx.toBase64()); ``` Ready for more? Check out the [tutorial](https://micro-bitcoin.github.io/#/tutorial/README) and start writing your very own hardware wallet! ================================================ FILE: examples/cpp/Makefile ================================================ TARGET ?= main ifeq ($(OS),Windows_NT) EXT ?= .exe else EXT ?= endif TARGET_EXEC ?= $(TARGET)$(EXT) # Paths # to make sure addprefix to LIB_DIR doesn't go out from build directory BUILD_DIR = build SRC_DIR = . # uBitcoin library LIB_DIR = ../../src # Tools ifeq ($(OS),Windows_NT) TOOLCHAIN_PREFIX ?= x86_64-w64-mingw32- MKDIR_P = mkdir RM_R = rmdir /s /q else TOOLCHAIN_PREFIX ?= MKDIR_P = mkdir -p RM_R = rm -r endif # compilers CC := $(TOOLCHAIN_PREFIX)gcc CXX := $(TOOLCHAIN_PREFIX)g++ # main.cpp CXX_SOURCES = $(wildcard $(SRC_DIR)/*.cpp) C_SOURCES = # uBitcoin sources CXX_SOURCES += $(wildcard $(LIB_DIR)/*.cpp) C_SOURCES += $(wildcard $(LIB_DIR)/utility/trezor/*.c) \ $(wildcard $(LIB_DIR)/utility/*.c) \ $(wildcard $(LIB_DIR)/*.c) # include lib path, don't use mbed or arduino config (-DUSE_STDONLY), debug symbols, all warnings as errors FLAGS = -I$(LIB_DIR) -g -Wall -Werror -ldl CFLAGS = $(FLAGS) CPPFLAGS = $(FLAGS) -DUSE_STDONLY -DUBTC_EXAMPLE OBJS = $(patsubst $(SRC_DIR)/%, $(BUILD_DIR)/src/%.o, \ $(patsubst $(LIB_DIR)/%, $(BUILD_DIR)/lib/%.o, \ $(C_SOURCES) $(CXX_SOURCES))) vpath %.cpp $(SRC_DIR) vpath %.cpp $(LIB_DIR) vpath %.c $(LIB_DIR) .PHONY: clean all run all: $(BUILD_DIR)/$(TARGET_EXEC) run: $(BUILD_DIR)/$(TARGET_EXEC) $(BUILD_DIR)/$(TARGET_EXEC) $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) $(CXX) $(OBJS) $(CPPFLAGS) -o $@ # lib c sources $(BUILD_DIR)/lib/%.c.o: %.c $(MKDIR_P) $(dir $@) $(CC) -c $(CFLAGS) $< -o $@ # lib cpp sources $(BUILD_DIR)/lib/%.cpp.o: %.cpp $(MKDIR_P) $(dir $@) $(CXX) -c $(CPPFLAGS) $< -o $@ # cpp sources $(BUILD_DIR)/src/%.cpp.o: %.cpp $(MKDIR_P) $(dir $@) $(CXX) -c $(CPPFLAGS) $< -o $@ clean: $(RM_R) $(BUILD_DIR) ================================================ FILE: examples/cpp/README.md ================================================ # C++ example run with `make run` ================================================ FILE: examples/cpp/main.cpp ================================================ /* * A simple example showing how to work with uBitcoin in c++ on a PC */ // only compile when UBTC_EXAMPLE flag is provided // to not clash with platformio's compile-everything approach #ifdef UBTC_EXAMPLE #include #include "Bitcoin.h" #include "PSBT.h" #include #include // You can define your random function to improve side-channel resistance extern "C" { // use system random function uint32_t random32(void){ return (uint32_t)rand(); } } using namespace std; char mnemonic[] = "flight canvas heart purse potato mixed offer tooth maple blue kitten salute almost staff physical remain coral clump midnight rotate innocent shield inch ski"; int main() { // convert mnemonic to xprv cout << "Your mnemonic: " << endl << mnemonic << endl; HDPrivateKey hd(mnemonic, ""); cout << "Your xprv: " << endl << string(hd) << endl; // derive account xpub char derivation[] = "m/84h/1h/0h"; HDPublicKey xpub = hd.derive(derivation).xpub(); // set network to regtest, otherwise default addresses will be for testnet xpub.network = &Regtest; cout << "Account xpub at path " << derivation << ":" << endl; cout << string(xpub) << endl; // print first 5 receiving addresses HDPublicKey recv_xpub = xpub.child(0); for (uint32_t i = 0; i < 5; i++) { cout << "Address #" << i << ": " << recv_xpub.child(i).address() << endl; } // signing PSBT transaction with 2 inputs PSBT psbt; psbt.parseBase64(string("cHNidP8BAJoCAAAAAqQW9JR6TFv46IXybtf9tKAy5WsYusr6O4rsfN8DIywEAQAAAAD9////9YKXV2aJad3wScN70cgZHMhQtwhTjw95loZfUB57+H4AAAAAAP3///8CwOHkAAAAAAAWABQzSSTq9G6AboazU3oS+BWVAw1zp21KTAAAAAAAFgAU2SSg4OQMonZrrLpdtTzcNes1MthDAQAAAAEAcQIAAAAB6GDWQUAnmq5s8Nm68qPp3fHnpARmx67Q5ZRHGj1rCjgBAAAAAP7///8CdIv2XwAAAAAWABRozVhYn14Pmv8XoAJePV7AQggf/4CWmAAAAAAAFgAUcOVKtnxrbE7ragGagzMqQ7kJsZkAAAAAAQEfgJaYAAAAAAAWABRw5Uq2fGtsTutqAZqDMypDuQmxmSIGA3s6OgE8GCKOcHDJe7XY0q/i/XSe6e933ErCDCCKR5WoGARkI4xUAACAAQAAgAAAAIAAAAAAAAAAAAABAHECAAAAAaH0XE8I0jQHvCDfdDTUbHrm9+oHbq1yt5ansxoaeeNjAQAAAAD+////AoCWmAAAAAAAFgAUQZD8n6hVi91tRSlWl4WkMwuBnoXsVTuMAAAAABYAFMbknFZNyqOzappeWfZi2+EP0asDAAAAAAEBH4CWmAAAAAAAFgAUQZD8n6hVi91tRSlWl4WkMwuBnoUiBgKNwymEX374HvJHU9FIT4YmCn8CuNteCOxtw7bJXGfscxgEZCOMVAAAgAEAAIAAAACAAAAAAAEAAAAAACICA9OwnpVPPgWAC/O7SuxHNPjX46Iz2Qv9dcI033AqEyv+GARkI4xUAACAAQAAgAAAAIABAAAAAAAAAAA=")); // check parsing is ok if(!psbt){ cout << "Failed parsing transaction" << endl; exit(EXIT_FAILURE); return EXIT_FAILURE; } cout << "Transactions details:" << endl; // going through all outputs cout << "Outputs:" << endl; for(unsigned int i = 0; i < psbt.tx.outputsNumber; i++){ // print addresses cout << psbt.tx.txOuts[i].address(&Regtest); if(psbt.txOutsMeta[i].derivationsLen > 0){ // there is derivation path // considering only single key for simplicity PSBTDerivation der = psbt.txOutsMeta[i].derivations[0]; HDPublicKey pub = hd.derive(der.derivation, der.derivationLen).xpub(); pub.network = &Regtest; if(pub.address() == psbt.tx.txOuts[i].address(&Regtest)){ cout << " (change) "; } } cout << " -> " << psbt.tx.txOuts[i].btcAmount()*1e3 << " mBTC" << endl; } cout << "Fee: " << psbt.fee() << " sat" << endl; // sign using our key psbt.sign(hd); cout << "Signed transaction:" << endl << psbt.toBase64() << endl; // now you can combine and finalize PSBTs in Bitcoin Core return 0; } #endif // UBTC_EXAMPLE ================================================ FILE: examples/ecdh/ecdh.ino ================================================ /* * This example shows how to use ECDH algorithm * to get shared secret between two parties. * It is useful if you want to establish * secure communication with someone else. * You can use ECDH to create symmetric key * and then use AES in CBC mode to encrypt/decrypt data. */ #include #include void setup(){ Serial.begin(9600); while(!Serial){ ; // wait for serial port to open } // generate random private keys and corresponding pubkeys byte secret1[32]; byte secret2[32]; for(int i=0; i // all single-line hashing algorithms #include // to print byte arrays in hex format void setup() { Serial.begin(9600); while(!Serial){ ; // wait for serial port to open } String message = "Hello world!"; // message to hash byte hash[64] = { 0 }; // hash int hashLen = 0; // sha-256 hashLen = sha256(message, hash); Serial.println("SHA-256: " + toHex(hash, hashLen)); Serial.println("Should be: c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a"); // you can also pass byte arrays to the hash function // and update piece by piece byte msg2[] = {1, 2, 3, 4, 5, 0xFA, 0xB0, 0x0B, 0x51}; // hash in one line sha256(msg2, sizeof(msg2), hash); // you can also create a class instance and use .write method SHA256 h; h.begin(); h.write(msg2, 5); // add first 5 bytes to hash h.write(msg2+5, 4); // add another 4 bytes h.end(hash); // result will be stored in the hash array // ripemd-160 hashLen = rmd160(message, hash); Serial.println("RMD-160: " + toHex(hash, hashLen)); Serial.println("Should be: 7f772647d88750add82d8e1a7a3e5c0902a346a3"); // hash160(msg) = rmd160(sha256(message)) hashLen = hash160(message, hash); Serial.println("Hash160: " + toHex(hash, hashLen)); Serial.println("Should be: 621281c15fb62d5c6013ea29007491e8b174e1b9"); // sha256(sha256(message)) hashLen = doubleSha(message, hash); Serial.println("DoubleSha: " + toHex(hash, hashLen)); Serial.println("Should be: 7982970534e089b839957b7e174725ce1878731ed6d700766e59cb16f1c25e27"); // sha512 hashLen = sha512(message, hash); Serial.println("Sha512: " + toHex(hash, hashLen)); Serial.println("Should be: f6cde2a0f819314cdde55fc227d8d7dae3d28cc556222a0a8ad66d91ccad4aad6094f517a2182360c9aacf6a3dc323162cb6fd8cdffedb0fe038f55e85ffb5b6"); // sha512-hmac // here we use more c-style approach char key[] = "Bitcoin seed"; hashLen = sha512Hmac((byte*)key, strlen(key), (byte*)message.c_str(), message.length(), hash); Serial.println("Sha512-HMAC: " + toHex(hash, hashLen)); Serial.println("Should be: f7fc496a2c17bd09a6328124dc6edebed987e7e93903deee0633a756f1ee81da0753334f6cfe226b5c712d893a68c547d3a5497cd73e1d010670c1e0e9d93a8a"); } void loop() { } ================================================ FILE: examples/mnemonic/mnemonic.ino ================================================ /* * This example shows how to derive master private key from the recovery seed * Generate a random recovery seed i.e. on https://iancoleman.io/bip39/ * and check the master private key, account private key, account public key * and first address. */ #include "Bitcoin.h" void printHD(String mnemonic, String password = ""){ HDPrivateKey hd(mnemonic, password); if(!hd){ // check if it is valid Serial.println("Invalid xpub"); return; } Serial.println("Mnemonic:"); Serial.println(mnemonic); Serial.print("Password: \""); Serial.println(password+"\""); Serial.println("Root private key:"); Serial.println(hd); Serial.println("bip84 master private key:"); HDPrivateKey account = hd.derive("m/84'/0'/0'/"); Serial.println(account); Serial.println("bip84 master public key:"); Serial.println(account.xpub()); Serial.println("first address:"); Serial.println(account.derive("m/0/0/").address()); Serial.println("\n"); } void setup() { Serial.begin(115200); printHD("arch volcano urge cradle turn labor skin secret squeeze denial jacket vintage fix glad lemon", "my secret password"); // entropy bytes to mnemonic uint8_t arr[] = {'1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'}; String mn = mnemonicFromEntropy(arr, sizeof(arr)); Serial.println(mn); uint8_t out[16]; size_t len = mnemonicToEntropy(mn, out, sizeof(out)); Serial.println(toHex(out, len)); } void loop() { delay(100); } ================================================ FILE: examples/multisig/multisig.ino ================================================ /* * A simple example showing how to derive multisig addresses */ #include "Bitcoin.h" // 2 of 3 multisig #define NUM_KEYS 3 #define THRESHOLD 2 // cosigners HDPublicKey xpubs[] = { // [73c5da0a/48h/1h/0h/2h] abandon * 11 + about HDPublicKey("Vpub5n95dMZrDHj6SeBgJ1oz4Fae2N2eJNuWK3VTKDb2dzGpMFLUHLmtyDfen7AaQxwQ5mZnMyXdVrkEaoMLVTH8FmVBRVWPGFYWhmtDUGehGmq"), // [fb7c1f11/48h/1h/0h/2h] ability * 11 + acid HDPublicKey("Vpub5mpiGVPWYfDWqZAvCtJihfU559GdwhNC5gwCa9xjSBp4Bvr1DnqXYTtAogkjvWYN1LGTyKo5Yjhfz7mNAqVmw7KG66CM4mDd8vGH3zPQmBH"), // [47fc1ba1/48h/1h/0h/2h] able * 11 + acid HDPublicKey("Vpub5nRyr5ptHEvisEDuWRMY3rgQ99B1CU21wfkuXEekTi8jCshCWseGqBWWZ8U3Wgv4jDj2fizxBNZmpFjo56Ffu49Efpu4vAj5XErHyBEQoN9"), }; void setup() { Serial.begin(); // print first 5 addresses for(int idx = 0; idx < 5; idx++){ // get derivation path String derivation = String("m/0/")+String(idx); Serial.println(derivation); // generate individual public keys and put them into array PublicKey pubkeys[NUM_KEYS]; for(int i=0; i 0){ // there is derivation path // considering only single key for simplicity PSBTDerivation der = psbt.txOutsMeta[i].derivations[0]; HDPublicKey pub = hd.derive(der.derivation, der.derivationLen).xpub(); if(pub.address() == psbt.tx.txOuts[i].address()){ Serial.print(" (change) "); } } Serial.print(" -> "); Serial.print(psbt.tx.txOuts[i].btcAmount()*1e3); Serial.println(" mBTC"); } Serial.print("Fee: "); Serial.print(float(psbt.fee())/100); // Arduino can't print 64-bit ints Serial.println(" bits"); psbt.sign(hd); Serial.println(psbt.toBase64()); // now you can combine and finalize PSBTs in Bitcoin Core } void loop() { } ================================================ FILE: examples/schnorr/schnorr.ino ================================================ /* * A simple example showing how to work with uBitcoin in c++ on a PC */ #include "Bitcoin.h" #include "Hash.h" char mnemonic[] = "flight canvas heart purse potato mixed offer tooth maple blue kitten salute almost staff physical remain coral clump midnight rotate innocent shield inch ski"; // convert mnemonic to xprv HDPrivateKey hd(mnemonic, ""); void setup() { Serial.begin(115200); Serial.println("Your mnemonic: "); Serial.println(mnemonic); // derive private key you want to use for schnorr PrivateKey prv = hd.derive("m/86h/0h/0h/0/1"); // print corresponding public key - schnorr keys are x-only 32-byte keys PublicKey pub = prv.publicKey(); Serial.print("Private key: "); Serial.println(prv); Serial.print("X-only public key: "); Serial.println(pub.x()); // sign message uint8_t msg[32]; sha256("hello world!", msg); // sign using Schnorr algorithm SchnorrSignature sig = prv.schnorr_sign(msg); // Unlike ECDSA, Schnorr signature is always 64-bytes long Serial.println("Signature for message 'hello world!':"); Serial.println(sig); // verify signatures if(pub.schnorr_verify(sig, msg)){ Serial.println("All good, signature is valid"); }else{ Serial.println("Something is wrong! Signature is invalid."); } } void loop() { } ================================================ FILE: examples/tx/tx.ino ================================================ #include #include "Bitcoin.h" void setup() { Serial.begin(9600); while(!Serial){ ; } // Single private key for testnet PrivateKey privateKey("cQwxqQwCwGoirnTkVnNt4XqJuEv24HYBvVWCTLtL5g1kx9Q1AEhE"); Serial.println(privateKey.address()); TxIn txIn("fbeae5f43d76fc3035cb4190baaf8cc123dd04f11c98c8f19a8b12cb4ce90db0", 0); // addresses to send bitcoins char destination[] = "n3DN9cswq5jnXXUmLP3bXtR89yfDNWrie9"; String change = privateKey.address(); // amounts to send // unsigned long can store up to 4294967295 satoshi (42.9 BTC) // for larger amounts use uint64_t unsigned long availableAmount = 2000000; // 58 mBTC unsigned long fee = 1500; unsigned long sendAmount = 1000000; // 20 mBTC unsigned long changeAmount = availableAmount - sendAmount - fee; TxOut txOutDestination(sendAmount, destination); TxOut txOutChange(changeAmount, change.c_str()); Serial.println(txOutDestination); // constructing actual transaction Tx tx; tx.addInput(txIn); tx.addOutput(txOutDestination); tx.addOutput(txOutChange); // Printing transaction information Serial.print("Tx length: "); Serial.println(tx.length()); Serial.print("Version: "); Serial.println(tx.version); Serial.print("Inputs: "); Serial.println(tx.inputsNumber); for(int i=0; i< tx.inputsNumber; i++){ TxIn txin = tx.txIns[i]; Serial.print("\tHash: "); Serial.println(toHex(txin.hash, 32)); Serial.print("\tOutput index: "); Serial.println(txin.outputIndex); Serial.print("\tScript length: "); Serial.println(txin.scriptSig.length()); Serial.print("\tScript: "); Serial.println(txin.scriptSig); Serial.print("\tSequence: "); Serial.println(txin.sequence); if(tx.isSegwit()){ Serial.println("\tSEGWIT!"); } } Serial.print("Outputs: "); Serial.println(tx.outputsNumber); for(int i=0; i< tx.outputsNumber; i++){ Serial.print(tx.txOuts[i].address()); Serial.print(": "); Serial.print(((float)tx.txOuts[i].amount)/100000); Serial.println(" mBTC"); } Serial.println("Unsigned transaction:"); Serial.println(tx); // signing transaction Serial.println("Signing transaction..."); Signature sig = tx.signInput(0, privateKey); Serial.println(sig); Serial.println("Signed transaction:"); Serial.println(tx); Serial.println("Transaction id:"); Serial.println(tx.txid()); Serial.println("Done"); } void loop() { // put your main code here, to run repeatedly: delay(100); } ================================================ FILE: examples/xpubs/xpubs.ino ================================================ /* * This example shows how to derive bitcoin addresses from the master public key (bip32) * Enter account master public key to the serial console and get the addresses * Use this tool to check: https://iancoleman.io/bip39/ */ #include "Bitcoin.h" void printAddresses(String pub){ HDPublicKey hd(pub); if(!hd){ // check if it is valid Serial.println("Invalid xpub"); return; } Serial.println("Master public key:"); Serial.println(hd); Serial.println("First 5 receiving addresses:"); for(int i=0; i<5; i++){ String path = String("m/0/")+i; Serial.print("Path:"); Serial.println(path); Serial.print("Address: "); Serial.println(hd.derive(path).address()); // Serial.print("Legacy: "); // Serial.println(hd.derive(path).legacyAddress()); // Serial.print("Nested segwit: "); // Serial.println(hd.derive(path).nestedSegwitAddress()); // Serial.print("Native segwit: "); // Serial.println(hd.derive(path).segwitAddress()); } Serial.println("\n"); } void setup() { Serial.begin(115200); // bip 44 printAddresses("xpub6BoiLSuHTDytgQejDauyXkBqQ4xTw2tSFKgkHfmZky7jDQQecUKwbFocxZXSJMCSp8gFNBbD9Squ3etZbJkE2qQqVBrypLjWJaWUNmHh3LT"); // bip 49 printAddresses("ypub6XMsccDqTfZCUz5m4VjbRLebnjFTLDpeKTgRJuUtAxkpQicB7p4ZwHdmimRuMTcunPfdpzgpVt7DCDKRRffsgmWavWLXccumbyYazC3wh5N"); // bip 84 printAddresses("zpub6rcGDMmj82CUJT1uV2mCcsN4EPTgBnJjciDWpYv12yCAPi9TEG5KLPF5iPtqzL6hNmaa5ZGfhJCHctoex7cGgqVxsyWcCDUNUDhjaYRQXzV"); Serial.println("Enter your master public key:"); } void loop() { if(Serial.available()){ String pub = Serial.readStringUntil('\n'); Serial.println(pub); if(pub.length() > 0){ printAddresses(pub); } } delay(100); } ================================================ FILE: keywords.txt ================================================ ####################################### # Syntax Coloring Map ####################################### ####################################### # Hash.h Methods and Functions (KEYWORD2) ####################################### rmd160 KEYWORD2 sha256 KEYWORD2 hash160 KEYWORD2 doubleSha KEYWORD2 sha512 KEYWORD2 sha512Hmac KEYWORD2 ####################################### # Datatypes and classes (KEYWORD1) ####################################### Bitcoin KEYWORD1 Hash KEYWORD1 Conversion KEYWORD1 OpCodes KEYWORD1 Scalar KEYWORD1 Point KEYWORD1 PrivateKey KEYWORD1 PublicKey KEYWORD1 HDPrivateKey KEYWORD1 HDPublicKey KEYWORD1 Script KEYWORD1 Signature KEYWORD1 SchnorrSignature KEYWORD1 Tx KEYWORD1 TxIn KEYWORD1 TxOut KEYWORD1 ElectrumTx KEYWORD1 PSBT KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### parse KEYWORD2 parseHex KEYWORD2 parseBase64 KEYWORD2 toBase64 KEYWORD2 serialize KEYWORD2 bin KEYWORD2 der KEYWORD2 type KEYWORD2 length KEYWORD2 scriptLength KEYWORD2 serializeScript KEYWORD2 push KEYWORD2 scriptPubkey KEYWORD2 address KEYWORD2 pow KEYWORD2 sign KEYWORD2 schnorr_sign KEYWORD2 verify KEYWORD2 schnorr_verify KEYWORD2 fromSeed KEYWORD2 fromMnemonic KEYWORD2 xpub KEYWORD2 xprv KEYWORD2 child KEYWORD2 hardenedChild KEYWORD2 derive KEYWORD2 legacyAddress KEYWORD2 segwitAddress KEYWORD2 nestedSegwitAddress KEYWORD2 parse KEYWORD2 fromWIF KEYWORD2 publicKey KEYWORD2 sec KEYWORD2 fromHex KEYWORD2 toHex KEYWORD2 toString KEYWORD2 generateMnemonic KEYWORD2 checkMnemonic KEYWORD2 littleEndianToInt KEYWORD2 intToLittleEndian KEYWORD2 bigEndianToInt KEYWORD2 intToBigEndian KEYWORD2 lenVarInt KEYWORD2 readVarInt KEYWORD2 readVarInt KEYWORD2 writeVarInt KEYWORD2 writeVarInt KEYWORD2 addInput KEYWORD2 addOutput KEYWORD2 signInput KEYWORD2 signSegwitInput KEYWORD2 sigHash KEYWORD2 ###################################### # Constants (LITERAL1) ####################################### PRIME LITERAL1 P2PKH LITERAL1 P2SH LITERAL1 P2WPKH LITERAL1 P2WSH LITERAL1 P2SH_P2WPKH LITERAL1 P2SH_P2WSH LITERAL1 SIGHASH_ALL LITERAL1 SIGHASH_NONE LITERAL1 SIGHASH_SINGLE LITERAL1 ###################################### # Opcodes (LITERAL1) ####################################### OP_0 LITERAL1 OP_PUSHDATA1 LITERAL1 OP_PUSHDATA2 LITERAL1 OP_PUSHDATA4 LITERAL1 OP_1NEGATE LITERAL1 OP_RESERVED LITERAL1 OP_1 LITERAL1 OP_2 LITERAL1 OP_3 LITERAL1 OP_4 LITERAL1 OP_5 LITERAL1 OP_6 LITERAL1 OP_7 LITERAL1 OP_8 LITERAL1 OP_9 LITERAL1 OP_10 LITERAL1 OP_11 LITERAL1 OP_12 LITERAL1 OP_13 LITERAL1 OP_14 LITERAL1 OP_15 LITERAL1 OP_16 LITERAL1 OP_NOP LITERAL1 OP_VER LITERAL1 OP_IF LITERAL1 OP_NOTIF LITERAL1 OP_VERIF LITERAL1 OP_VERNOTIF LITERAL1 OP_ELSE LITERAL1 OP_ENDIF LITERAL1 OP_VERIFY LITERAL1 OP_RETURN LITERAL1 OP_TOALTSTACK LITERAL1 OP_FROMALTSTACK LITERAL1 OP_2DROP LITERAL1 OP_2DUP LITERAL1 OP_3DUP LITERAL1 OP_2OVER LITERAL1 OP_2ROT LITERAL1 OP_2SWAP LITERAL1 OP_IFDUP LITERAL1 OP_DEPTH LITERAL1 OP_DROP LITERAL1 OP_DUP LITERAL1 OP_NIP LITERAL1 OP_OVER LITERAL1 OP_PICK LITERAL1 OP_ROLL LITERAL1 OP_ROT LITERAL1 OP_SWAP LITERAL1 OP_TUCK LITERAL1 OP_CAT LITERAL1 OP_SUBSTR LITERAL1 OP_LEFT LITERAL1 OP_RIGHT LITERAL1 OP_SIZE LITERAL1 OP_INVERT LITERAL1 OP_AND LITERAL1 OP_OR LITERAL1 OP_XOR LITERAL1 OP_EQUAL LITERAL1 OP_EQUALVERIFY LITERAL1 OP_RESERVED1 LITERAL1 OP_RESERVED2 LITERAL1 OP_1ADD LITERAL1 OP_1SUB LITERAL1 OP_2MUL LITERAL1 OP_2DIV LITERAL1 OP_NEGATE LITERAL1 OP_ABS LITERAL1 OP_NOT LITERAL1 OP_0NOTEQUAL LITERAL1 OP_ADD LITERAL1 OP_SUB LITERAL1 OP_MUL LITERAL1 OP_DIV LITERAL1 OP_MOD LITERAL1 OP_LSHIFT LITERAL1 OP_RSHIFT LITERAL1 OP_BOOLAND LITERAL1 OP_BOOLOR LITERAL1 OP_NUMEQUAL LITERAL1 OP_NUMEQUALVERIFY LITERAL1 OP_NUMNOTEQUAL LITERAL1 OP_LESSTHAN LITERAL1 OP_GREATERTHAN LITERAL1 OP_LESSTHANOREQUAL LITERAL1 OP_GREATERTHANOREQUAL LITERAL1 OP_MIN LITERAL1 OP_MAX LITERAL1 OP_WITHIN LITERAL1 OP_RIPEMD160 LITERAL1 OP_SHA1 LITERAL1 OP_SHA256 LITERAL1 OP_HASH160 LITERAL1 OP_HASH256 LITERAL1 OP_CODESEPARATOR LITERAL1 OP_CHECKSIG LITERAL1 OP_CHECKSIGVERIFY LITERAL1 OP_CHECKMULTISIG LITERAL1 OP_CHECKMULTISIGVERIFY LITERAL1 OP_NOP1 LITERAL1 OP_CHECKLOCKTIMEVERIFY LITERAL1 OP_CHECKSEQUENCEVERIFY LITERAL1 OP_NOP4 LITERAL1 OP_NOP5 LITERAL1 OP_NOP6 LITERAL1 OP_NOP7 LITERAL1 OP_NOP8 LITERAL1 OP_NOP9 LITERAL1 OP_NOP10 LITERAL1 OP_NULLDATA LITERAL1 OP_PUBKEYHASH LITERAL1 OP_PUBKEY LITERAL1 OP_INVALIDOPCODE LITERAL1 ================================================ FILE: library.json ================================================ { "name": "uBitcoin", "version": "0.2.0", "description": "Brings Bitcoin to embedded devices. Write your own hardware wallet, vending machine or any other bitcoin-powered device. Supports public and private keys, HD wallets, transactions and scripts. Everything required to start working with Bitcoin on microcontrollers.", "keywords": "bitcoin, cryptography", "repository": { "type": "git", "url": "https://github.com/micro-bitcoin/uBitcoin.git" }, "authors": [ { "name": "Stepan Snigirev", "email": "snigirev.stepan@gmail.com", "url": "https://stepansnigirev.com" } ], "license": "MIT", "homepage": "https://micro-bitcoin.github.io", "dependencies": { }, "frameworks": "*", "platforms": "*" } ================================================ FILE: library.properties ================================================ name=uBitcoin version=0.2.0 author=Stepan Snigirev maintainer=Stepan Snigirev sentence=Brings Bitcoin to embedded devices paragraph=Write your own hardware wallet, vending machine or any other bitcoin-powered device. Supports public and private keys, HD wallets, transactions and scripts. Everything required to start working with Bitcoin on microcontrollers. category=Data Processing url=https://github.com/micro-bitcoin/uBitcoin architectures=* ================================================ FILE: src/BaseClasses.cpp ================================================ #include "uBitcoin_conf.h" #include "BaseClasses.h" #include #if USE_STD_STRING using std::string; #define String string #endif size_t SerializeStream::serialize(const Streamable * s, size_t offset){ return s->to_stream(this, offset); } size_t ParseStream::parse(Streamable * s){ return s->from_stream(this); } /************ Parse Byte Stream Class ************/ ParseByteStream::ParseByteStream(const uint8_t * arr, size_t length, encoding_format f){ last = -1; format = f; cursor = 0; len = (arr == NULL) ? 0 : length; buf = arr; } // TODO: call prev constructor ParseByteStream::ParseByteStream(const char * arr, encoding_format f){ last = -1; format = f; cursor = 0; len = (arr == NULL) ? 0 : strlen(arr); buf = (const uint8_t *) arr; } ParseByteStream::~ParseByteStream(){ buf = NULL; } size_t ParseByteStream::available(){ if(format == HEX_ENCODING){ return (len - cursor)/2; }else{ return len-cursor; } }; int ParseByteStream::read(){ if(format == HEX_ENCODING){ if(cursor < len-1){ uint8_t c1 = hexToVal(buf[cursor]); uint8_t c2 = hexToVal(buf[cursor+1]); if(c1 < 0x10 && c2 < 0x10){ cursor +=2; last = (c1<<4) + c2; return last; } } }else{ if(cursor < len){ uint8_t c = buf[cursor]; cursor ++; last = c; return c; } } return -1; } int ParseByteStream::getLast(){ return last; } size_t ParseByteStream::read(uint8_t *arr, size_t length){ size_t cc = 0; while(cc 0){ if(format == HEX_ENCODING){ buf[cursor] = ((b >> 4) & 0x0F) + '0'; if(buf[cursor] > '9'){ buf[cursor] += 'a'-'9'-1; } cursor++; buf[cursor] = (b & 0x0F) + '0'; if(buf[cursor] > '9'){ buf[cursor] += 'a'-'9'-1; } cursor++; }else{ buf[cursor] = b; cursor++; } return 1; } return 0; }; size_t SerializeByteStream::write(const uint8_t *arr, size_t length){ size_t l = 0; while(available()>0 && l < length){ write(arr[l]); l++; } return l; }; /************ Readable Class ************/ #ifdef ARDUINO size_t Readable::printTo(Print& p) const{ size_t len = this->stringLength()+1; char * arr = (char *)calloc(len, sizeof(char)); toString(arr, len); p.print(arr); free(arr); return len-1; } #endif #if USE_ARDUINO_STRING || USE_STD_STRING String Readable::toString() const{ size_t len = this->stringLength()+1; char * arr = (char *)calloc(len, sizeof(char)); toString(arr, len); String s = arr; free(arr); return s; }; #endif /************ Streamable Class ************/ #if USE_ARDUINO_STRING || USE_STD_STRING String Streamable::serialize(size_t offset, size_t len) const{ if(len == 0){ len = (length()-offset); } char * arr = (char *)calloc(2*len+1, sizeof(char)); serialize(arr, 2*len, offset, HEX_ENCODING); String s = arr; free(arr); return s; }; #endif size_t Streamable::serialize(uint8_t * arr, size_t len, size_t offset, encoding_format format) const{ SerializeByteStream s(arr, len, format); return to_stream(&s, offset); } ================================================ FILE: src/BaseClasses.h ================================================ #ifndef __BITCOIN_BASE_CLASSES_H__ #define __BITCOIN_BASE_CLASSES_H__ #include "uBitcoin_conf.h" #include "Conversion.h" #include #include enum encoding_format{ RAW = 0, HEX_ENCODING = 16 // TODO: would be nice to have base64 encoding here }; enum parse_status{ PARSING_DONE = 0, PARSING_INCOMPLETE = 1, PARSING_FAILED = 2 }; // error codes struct / class? class Streamable; class ParseStream{ public: virtual size_t available(){ return 0; }; virtual int read(){ return -1; }; virtual size_t read(uint8_t *arr, size_t length){ return 0; }; virtual int getLast(){ return -1; }; size_t parse(Streamable * s); }; // TODO: skip leading non-hex if it's hex format class ParseByteStream: public ParseStream{ private: const uint8_t * buf; size_t cursor; size_t len; encoding_format format; int last; public: ParseByteStream(const uint8_t * arr, size_t length, encoding_format f=RAW); ParseByteStream(const char * arr, encoding_format f=HEX_ENCODING); ~ParseByteStream(); size_t available(); int read(); size_t read(uint8_t *arr, size_t length); virtual int getLast(); }; class SerializeStream{ public: virtual size_t available(){ return 0; }; virtual size_t write(uint8_t b){ return 0; }; virtual size_t write(const uint8_t *arr, size_t len){ return 0; }; size_t serialize(const Streamable * s, size_t offset); }; class SerializeByteStream: public SerializeStream{ private: uint8_t * buf; size_t cursor; size_t len; encoding_format format; public: SerializeByteStream(uint8_t * arr, size_t length, encoding_format f=RAW); explicit SerializeByteStream(char * arr, size_t length, encoding_format f=HEX_ENCODING); size_t available(); size_t write(uint8_t b); size_t write(const uint8_t *arr, size_t length); }; /** Abstract Readable class that can be encoded as a string and displayed to the user * Can be converted to and from a string (char *, Arduino String and std::string) * In Arduino it can be directly printed to the serial port, display or other Print device */ #ifdef ARDUINO class Readable: public Printable{ #else class Readable{ #endif private: protected: /* override these functions in your class */ virtual size_t to_str(char * buf, size_t len) const = 0; virtual size_t from_str(const char * buf, size_t len) = 0; public: /* override these function in your class */ virtual size_t stringLength() const = 0; size_t toString(char * buf, size_t len) const{ return this->to_str(buf, len); }; size_t fromString(const char * buf, size_t len){ return this->from_str(buf, len); }; size_t fromString(const char * buf){ return this->from_str(buf, strlen(buf)); }; #ifdef ARDUINO size_t printTo(Print& p) const; #endif #if USE_ARDUINO_STRING String toString() const; operator String(){ return this->toString(); }; #endif #if USE_STD_STRING std::string toString() const; operator std::string(){ return this->toString(); }; #endif }; /** Abstract Streamable class that can be serialized to/from a sequence of bytes * and sent to Stream (File, Serial, Bluetooth) without allocating extra memory * Class can be parsed and serialized in raw and hex formats */ class Streamable: public Readable{ friend class SerializeStream; friend class ParseStream; private: protected: virtual size_t from_stream(ParseStream *s) = 0; virtual size_t to_stream(SerializeStream *s, size_t offset) const = 0; virtual size_t to_str(char * buf, size_t len) const{ return serialize(buf, len); } virtual size_t from_str(const char * buf, size_t len){ return parse(buf, len); } parse_status status; size_t bytes_parsed; public: Streamable() { status = PARSING_DONE; bytes_parsed = 0; }; virtual void reset(){ status = PARSING_DONE; bytes_parsed = 0; }; // used to reset parsing and mb object virtual size_t length() const = 0; virtual size_t stringLength() const{ return 2*length(); }; /** \brief Gets parsing status. * PARSING_DONE - everything is ok, * PARSING_INCOMPLETE - some data is still missing * PARSING_FAILED - something went wrong, the data is probably incorrect. */ parse_status getStatus(){ return status; }; /** \brief Sets parsing status. Should be used with care. */ void setStatus(parse_status s){ status = s; }; size_t parse(const uint8_t * arr, size_t len, encoding_format format=RAW){ ParseByteStream s(arr, len, format); return from_stream(&s); } #if USE_ARDUINO_STRING size_t parse(String arr, encoding_format format=HEX_ENCODING){ return parse(arr.c_str(), strlen(arr.c_str()), format); } #endif #if USE_STD_STRING size_t parse(std::string arr, encoding_format format=HEX_ENCODING){ return parse(arr.c_str(), strlen(arr.c_str()), format); } #endif #if !(USE_ARDUINO_STRING || USE_STD_STRING) size_t parse(const char * arr, encoding_format format=HEX_ENCODING){ return parse(arr, strlen(arr), format); } #endif size_t serialize(uint8_t * arr, size_t len, size_t offset = 0, encoding_format format=RAW) const; size_t parse(const char * arr, size_t len, encoding_format format=HEX_ENCODING){ return parse((const uint8_t *) arr, len, format); } size_t serialize(char * arr, size_t len, size_t offset = 0, encoding_format format=HEX_ENCODING) const{ return serialize((uint8_t *)arr, len, offset, format); } #if USE_ARDUINO_STRING String serialize(size_t offset=0, size_t len=0) const; #endif #if USE_STD_STRING std::string serialize(size_t offset=0, size_t len=0) const; std::string serialize(int offset, int len) const{ return serialize((size_t)offset, (size_t)len); }; std::string serialize(size_t offset, int len) const{ return serialize((size_t)offset, (size_t)len); }; #endif }; #endif // __BITCOIN_BASE_CLASSES_H__ ================================================ FILE: src/Bitcoin.cpp ================================================ #include "Bitcoin.h" #include "Hash.h" #include "Conversion.h" #include #include #include "utility/trezor/sha2.h" #include "utility/trezor/rfc6979.h" #include "utility/trezor/ecdsa.h" #include "utility/trezor/secp256k1.h" #include "utility/segwit_addr.h" #include "utility/trezor/bip39.h" #include "utility/trezor/memzero.h" #if USE_STD_STRING using std::string; #define String std::string #endif // error code when parsing fails int ubtc_errno = 0; const char * generateMnemonic(uint8_t numWords){ if(numWords<12 || numWords > 24 || numWords % 3 != 0){ return NULL; } int strength = numWords*32/3; return mnemonic_generate(strength); } const char * generateMnemonic(uint8_t numWords, const uint8_t * entropy_data, size_t dataLen){ if(numWords<12 || numWords > 24 || numWords % 3 != 0){ return NULL; } uint8_t hash[32]; sha256(entropy_data, dataLen, hash); size_t len = numWords*4/3; return mnemonic_from_data(hash, len); } const char * mnemonicFromEntropy(const uint8_t * entropy_data, size_t dataLen){ return mnemonic_from_data(entropy_data, dataLen); } size_t mnemonicToEntropy(const char * mnemonic, size_t mnemonicLen, uint8_t * output, size_t outputLen){ int num_words = 1; for (size_t i = 0; i < strlen(mnemonic); i++){ if(mnemonic[i] == ' '){ num_words ++; } } size_t entropy_len = (num_words*4)/3; if(outputLen < entropy_len){ return 0; } uint8_t res[33] = {0}; int r = mnemonic_to_entropy(mnemonic, res); if(r == 0){ return 0; } memcpy(output, res, entropy_len); return entropy_len; } #if USE_ARDUINO_STRING || USE_STD_STRING size_t mnemonicToEntropy(String mnemonic, uint8_t * output, size_t outputLen){ return mnemonicToEntropy(mnemonic.c_str(), mnemonic.length(), output, outputLen); } #else size_t mnemonicToEntropy(char * mnemonic, uint8_t * output, size_t outputLen){ return mnemonicToEntropy(mnemonic, strlen(mnemonic), output, outputLen); } #endif const char * generateMnemonic(const uint8_t * entropy_data, size_t dataLen){ return generateMnemonic(24, entropy_data, dataLen); } #if !(USE_ARDUINO_STRING || USE_STD_STRING) const char * generateMnemonic(uint8_t numWords, const char * entropy_string){ return generateMnemonic(numWords, (uint8_t*)entropy_string, strlen(entropy_string)); } const char * generateMnemonic(const char * entropy_string){ return generateMnemonic(24, entropy_string); } bool checkMnemonic(const char * mnemonic){ return mnemonic_check(mnemonic); } #else const char * generateMnemonic(uint8_t numWords, const String entropy_string){ return generateMnemonic(numWords, (uint8_t*)entropy_string.c_str(), strlen(entropy_string.c_str())); } const char * generateMnemonic(const String entropy_string){ return generateMnemonic(24, entropy_string); } bool checkMnemonic(const String mnemonic){ return mnemonic_check(mnemonic.c_str()); } #endif // ---------------------------------------------------------------- Signature class Signature::Signature(){ memzero(tot, 3); index = 0; memzero(r, 32); memzero(s, 32); } Signature::Signature(const uint8_t r_arr[32], const uint8_t s_arr[32]){ memzero(tot, 3); index = 0; memcpy(r, r_arr, 32); memcpy(s, s_arr, 32); } Signature::Signature(const uint8_t * der){ memzero(tot, 3); index = 0; memzero(r, 32); memzero(s, 32); fromDer(der, der[1]+2); } Signature::Signature(const uint8_t * der, size_t derLen){ memzero(tot, 3); index = 0; memzero(r, 32); memzero(s, 32); fromDer(der, derLen); } Signature::Signature(const char * der){ memzero(tot, 3); index = 0; memzero(r, 32); memzero(s, 32); ParseByteStream s(der); Signature::from_stream(&s); } size_t Signature::from_stream(ParseStream *stream){ // der encoding is probably the most uneffective way to encode signatures... // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] // * total-length: 1-byte length descriptor of everything that follows // * R-length: 1-byte length descriptor of the R value that follows. // * R: arbitrary-length big-endian encoded R value. It must use the shortest // possible encoding for a positive integers (which means no null bytes at // the start, except a single one when the next byte has its highest bit set). // * S-length: 1-byte length descriptor of the S value that follows. // * S: arbitrary-length big-endian encoded S value. The same rules apply. if(status == PARSING_FAILED){ return 0; } if(status == PARSING_DONE){ bytes_parsed = 0; memzero(tot, 3); memzero(r, 32); memzero(s, 32); } status = PARSING_INCOMPLETE; size_t bytes_read = 0; uint8_t c = 0; if(stream->available() && bytes_parsed+bytes_read < 1){ c = stream->read(); bytes_read++; if(c!=0x30){ status = PARSING_FAILED; return bytes_read; } } if(stream->available() && bytes_parsed+bytes_read < 2){ tot[0] = stream->read(); bytes_read++; if(tot[0] > 70){ status = PARSING_FAILED; return bytes_read; } } // r if(stream->available() && bytes_parsed+bytes_read < 3){ c = stream->read(); bytes_read++; if(c != 0x02){ status = PARSING_FAILED; return bytes_read; } } if(stream->available() && bytes_parsed+bytes_read < 4){ tot[1] = stream->read(); bytes_read++; if(tot[1] > 33){ status = PARSING_FAILED; return bytes_read; } } if(stream->available() && tot[1]==33 && bytes_parsed+bytes_read < 5){ c = stream->read(); bytes_read++; if(c != 0){ status = PARSING_FAILED; return bytes_read; } } while(stream->available() && bytes_parsed+bytes_read < (size_t)4+tot[1]){ r[bytes_parsed+bytes_read-4+32-tot[1]] = stream->read(); bytes_read++; } if(rlen() != tot[1]){ status = PARSING_FAILED; return bytes_read; } // s if(stream->available() && bytes_parsed+bytes_read < (size_t)4+tot[1]+1){ c = stream->read(); bytes_read++; if(c != 0x02){ status = PARSING_FAILED; return bytes_read; } } if(stream->available() && bytes_parsed+bytes_read < (size_t)4+tot[1]+2){ tot[2] = stream->read(); bytes_read++; if(tot[2] > 33){ status = PARSING_FAILED; return bytes_read; } } if(stream->available() && tot[2]==33 && bytes_parsed+bytes_read < (size_t)4+tot[1]+3){ c = stream->read(); bytes_read++; if(c != 0){ status = PARSING_FAILED; return bytes_read; } } while(stream->available() && bytes_parsed+bytes_read < (size_t)4+tot[1]+2+tot[2]){ s[bytes_parsed+bytes_read-4-tot[1]-2-tot[2]+32] = stream->read(); bytes_read++; } if(slen() != tot[2]){ status = PARSING_FAILED; return bytes_read; } if(bytes_parsed+bytes_read == (size_t)4+tot[1]+2+tot[2]){ status = PARSING_DONE; } bytes_parsed+=bytes_read; return bytes_read; } size_t Signature::to_stream(SerializeStream *stream, size_t offset) const{ uint8_t arr[72]; der(arr, sizeof(arr)); size_t l = Signature::length(); size_t bytes_written = 0; while(stream->available() && offset+bytes_written < l){ stream->write(arr[offset+bytes_written]); bytes_written++; } return bytes_written; } size_t Signature::rlen() const{ uint8_t len = 33; for(int i=0; i<32; i++){ if(r[i] > 0){ if(r[i] < 0x80){ len --; } break; }else{ len--; } } return len; } size_t Signature::slen() const{ uint8_t len = 33; for(int i=0; i<32; i++){ if(s[i] > 0){ if(s[i] < 0x80){ len --; } break; }else{ len--; } } return len; } size_t Signature::length() const{ return rlen()+slen()+6; } size_t Signature::fromDer(const uint8_t * raw, size_t rawLen){ ParseByteStream stream(raw, rawLen); return Signature::from_stream(&stream); } size_t Signature::der(uint8_t * bytes, size_t len) const{ memzero(bytes, len); uint8_t _rlen = rlen(); uint8_t _slen = slen(); bytes[0] = 0x30; bytes[1] = 4+_rlen+2+_slen-2; bytes[2] = 0x02; bytes[3] = _rlen; if(_rlen == 33){ memcpy(bytes+5, r, 32); }else{ memcpy(bytes+4, r+32-_rlen, _rlen); } bytes[4+_rlen] = 0x02; bytes[4+_rlen+1] = _slen; if(_slen == 33){ memcpy(bytes+4+_rlen+3, s, 32); }else{ memcpy(bytes+4+_rlen+2, s+32-_slen, _slen); } return 4+_rlen+2+_slen; } void Signature::bin(uint8_t * arr, size_t len) const{ size_t l = len; if(l > 32){ l = 32; } memcpy(arr, r, l); if(len > 32){ l = len-32; if(l > 32){ l = 32; } memcpy(arr+32, s, l); } if(len > 64){ arr[64] = index; } } void Signature::fromBin(const uint8_t * arr, size_t len){ size_t l = len; if(l > 32){ l = 32; } memcpy(r, arr, l); if(len > 32){ l = len-32; if(l > 32){ l = 32; } memcpy(s, arr+32, l); } if(len > 64){ index = arr[64]; } } // ---------------------------------------------------------------- SchnorrSignature SchnorrSignature::SchnorrSignature(){ memzero(r, 32); memzero(s, 32); } SchnorrSignature::SchnorrSignature(const uint8_t r_arr[32], const uint8_t s_arr[32]){ memcpy(r, r_arr, 32); memcpy(s, s_arr, 32); } SchnorrSignature::SchnorrSignature(const uint8_t rs_arr[64]){ memcpy(r, rs_arr, 32); memcpy(s, rs_arr+32, 32); } SchnorrSignature::SchnorrSignature(const char * rs){ memzero(r, 32); memzero(s, 32); ParseByteStream s(rs); SchnorrSignature::from_stream(&s); } size_t SchnorrSignature::from_stream(ParseStream *stream){ if(status == PARSING_FAILED){ return 0; } if(status == PARSING_DONE){ bytes_parsed = 0; memzero(r, 32); memzero(s, 32); } status = PARSING_INCOMPLETE; size_t bytes_read = 0; while(stream->available() && bytes_parsed+bytes_read < 32){ r[bytes_read+bytes_parsed] = stream->read(); bytes_read++; } while(stream->available() && bytes_parsed+bytes_read < 64){ s[bytes_read+bytes_parsed-32] = stream->read(); bytes_read++; } if(bytes_parsed+bytes_read == 64){ status = PARSING_DONE; } bytes_parsed+=bytes_read; return bytes_read; } size_t SchnorrSignature::to_stream(SerializeStream *stream, size_t offset) const{ uint8_t arr[64]; memcpy(arr, r, 32); memcpy(arr+32, s, 32); size_t l = sizeof(arr); size_t bytes_written = 0; while(stream->available() && offset+bytes_written < l){ stream->write(arr[offset+bytes_written]); bytes_written++; } return bytes_written; } // ---------------------------------------------------------------- PublicKey class int PublicKey::legacyAddress(char * address, size_t len, const Network * network) const{ memzero(address, len); uint8_t buffer[20]; uint8_t sec_arr[65] = { 0 }; int l = sec(sec_arr, sizeof(sec_arr)); hash160(sec_arr, l, buffer); uint8_t addr[21]; addr[0] = network->p2pkh; memcpy(addr+1, buffer, 20); return toBase58Check(addr, 21, address, len); } #if USE_ARDUINO_STRING || USE_STD_STRING String PublicKey::legacyAddress(const Network * network) const{ char addr[40] = { 0 }; legacyAddress(addr, sizeof(addr), network); return String(addr); } #endif int PublicKey::segwitAddress(char address[], size_t len, const Network * network) const{ memzero(address, len); if(len < 76){ // TODO: 76 is too much for native segwit return 0; } uint8_t hash[20]; uint8_t sec_arr[65] = { 0 }; int l = sec(sec_arr, sizeof(sec_arr)); hash160(sec_arr, l, hash); segwit_addr_encode(address, network->bech32, 0, hash, 20); return 76; } #if USE_ARDUINO_STRING || USE_STD_STRING String PublicKey::segwitAddress(const Network * network) const{ char addr[76] = { 0 }; segwitAddress(addr, sizeof(addr), network); return String(addr); } #endif int PublicKey::nestedSegwitAddress(char address[], size_t len, const Network * network) const{ memzero(address, len); uint8_t script[22] = { 0 }; // script[0] = 0x00; // no need to set - already zero script[1] = 0x14; uint8_t sec_arr[65] = { 0 }; int l = sec(sec_arr, sizeof(sec_arr)); hash160(sec_arr, l, script+2); uint8_t addr[21]; addr[0] = network->p2sh; hash160(script, 22, addr+1); return toBase58Check(addr, 21, address, len); } #if USE_ARDUINO_STRING || USE_STD_STRING String PublicKey::nestedSegwitAddress(const Network * network) const{ char addr[40] = { 0 }; nestedSegwitAddress(addr, sizeof(addr), network); return String(addr); } #endif Script PublicKey::script(ScriptType type) const{ return Script(*this, type); } bool PublicKey::verify(const Signature sig, const uint8_t hash[32]) const{ uint8_t signature[64] = {0}; sig.bin(signature, 64); uint8_t pub[65]; serialize(pub, 65); return (ecdsa_verify_digest(&secp256k1, pub, signature, hash)==0); } bool PublicKey::schnorr_verify(const SchnorrSignature sig, const uint8_t hash[32]) const{ PublicKey pub = *this; if(!pub.isEven()){ pub = -pub; } uint8_t rs[64]; sig.serialize(rs, sizeof(rs)); PublicKey R; R.from_x(rs, 32); // calculate hash using tagged hash with "BIP0340/challenge" prefix uint8_t e[32]; uint8_t tmp[32]; TaggedHash tch("BIP0340/challenge"); // write R tch.write(rs, 32); // write xonly pubkey pub.x(tmp, sizeof(tmp)); tch.write(tmp, sizeof(tmp)); // write message tch.write(hash, 32); tch.end(e); PrivateKey challenge(e); PublicKey S = challenge*pub + R; S.x(tmp, sizeof(tmp)); PrivateKey s(rs+32); s.publicKey().x(e, 32); return memcmp(tmp, e, 32) == 0; }; // ---------------------------------------------------------------- PrivateKey class size_t PrivateKey::from_stream(ParseStream *s){ if(status == PARSING_FAILED){ return 0; } if(status == PARSING_DONE){ bytes_parsed = 0; } status = PARSING_INCOMPLETE; size_t bytes_read = 0; while(s->available() > 0 && bytes_parsed+bytes_read < 32){ num[bytes_parsed+bytes_read] = s->read(); bytes_read++; } if(bytes_parsed+bytes_read == 32){ status = PARSING_DONE; uint8_t zero[32] = { 0 }; if(memcmp(num, zero, 32)==0){ // should we add something else here? status = PARSING_FAILED; } bignum256 n; bn_read_be(num, &n); bn_mod(&n, &secp256k1.order); bn_write_be(&n, num); pubKey = *this * GeneratorPoint; } bytes_parsed += bytes_read; return bytes_read; } PrivateKey::PrivateKey(void){ reset(); memzero(num, 32); // empty key network = &DEFAULT_NETWORK; } PrivateKey::PrivateKey(const uint8_t * secret_arr, bool use_compressed, const Network * net){ reset(); memcpy(num, secret_arr, 32); network = net; pubKey = *this * GeneratorPoint; pubKey.compressed = use_compressed; } PrivateKey::PrivateKey(const ECScalar other){ reset(); other.getSecret(num); pubKey = *this * GeneratorPoint; pubKey.compressed = true; network = &DEFAULT_NETWORK; } /*PrivateKey &PrivateKey::operator=(const PrivateKey &other){ if (this == &other){ return *this; } // self-assignment reset(); other.getSecret(num); network = other.network; pubKey = *this * GeneratorPoint; pubKey.compressed = other.pubKey.compressed; return *this; };*/ PrivateKey::~PrivateKey(void) { reset(); // erase secret key from memory memzero(num, 32); } int PrivateKey::wif(char * wifArr, size_t wifSize) const{ memzero(wifArr, wifSize); uint8_t wifHex[34] = { 0 }; // prefix + 32 bytes secret (+ compressed ) size_t len = 33; wifHex[0] = network->wif; memcpy(wifHex+1, num, 32); if(pubKey.compressed){ wifHex[33] = 0x01; len++; } size_t l = toBase58Check(wifHex, len, wifArr, wifSize); memzero(wifHex, sizeof(wifHex)); // secret should not stay in RAM return l; } #if USE_ARDUINO_STRING || USE_STD_STRING String PrivateKey::wif() const{ char wifString[53] = { 0 }; wif(wifString, sizeof(wifString)); return String(wifString); } #endif int PrivateKey::fromWIF(const char * wifArr, size_t wifSize){ uint8_t arr[40] = { 0 }; size_t l = fromBase58Check(wifArr, wifSize, arr, sizeof(arr)); if( (l < 33) || (l > 34) ){ memzero(num, 32); return 0; } bool compressed; network = &DEFAULT_NETWORK; bool found = false; for(int i=0; iwif){ network = networks[i]; found = true; break; } } if(!found){ return 0; } if(l == 34){ compressed = (arr[33] > 0); } if(l == 33){ compressed = false; } memcpy(num, arr+1, 32); memzero(arr, 40); // clear memory pubKey = *this * GeneratorPoint; pubKey.compressed = compressed; return 1; } int PrivateKey::fromWIF(const char * wifArr){ return fromWIF(wifArr, strlen(wifArr)); } PublicKey PrivateKey::publicKey() const{ return pubKey; } int PrivateKey::address(char * address, size_t len) const{ return pubKey.address(address, len, network); } int PrivateKey::legacyAddress(char * address, size_t len) const{ return pubKey.legacyAddress(address, len, network); } int PrivateKey::segwitAddress(char * address, size_t len) const{ return pubKey.segwitAddress(address, len, network); } int PrivateKey::nestedSegwitAddress(char * address, size_t len) const{ return pubKey.nestedSegwitAddress(address, len, network); } #if USE_ARDUINO_STRING || USE_STD_STRING String PrivateKey::address() const{ return pubKey.address(network); } String PrivateKey::legacyAddress() const{ return pubKey.legacyAddress(network); } String PrivateKey::segwitAddress() const{ return pubKey.segwitAddress(network); } String PrivateKey::nestedSegwitAddress() const{ return pubKey.nestedSegwitAddress(network); } #endif int PrivateKey::ecdh(const PublicKey pub, uint8_t shared_secret[32], bool use_hash){ // calculate pk * pub, serialize as uncompressed point and hash ECPoint mult = *this * pub; mult.compressed = false; uint8_t sec[65]; mult.sec(sec, sizeof(sec)); if(use_hash){ sha256(sec+1, 64, shared_secret); }else{ // just copy x memcpy(shared_secret, sec+1, 32); } return 1; } static int is_canonical(uint8_t by, uint8_t sig[64]){ return 1; } Signature PrivateKey::sign(const uint8_t hash[32]) const{ uint8_t signature[65] = {0}; uint8_t i = 0; ecdsa_sign_digest(&secp256k1, num, hash, signature, &i, &is_canonical); Signature sig(signature, signature+32); sig.index = i; return sig; } SchnorrSignature PrivateKey::schnorr_sign(const uint8_t hash[32]) const{ PrivateKey prv = *this; PublicKey pub = prv.publicKey(); // check if pubkey is even, if not - negate if(!pub.isEven()){ prv = -prv; pub = -pub; } uint8_t r[32]; uint8_t s[32]; uint8_t tmp[32]; // generate k using tagged hash with "BIP0340/nonce" prefix uint8_t nonce[32]; TaggedHash tnonce("BIP0340/nonce"); prv.getSecret(tmp); tnonce.write(tmp, sizeof(tmp)); pub.x(tmp, sizeof(tmp)); tnonce.write(tmp, sizeof(tmp)); tnonce.write(hash, 32); tnonce.end(nonce); PrivateKey k(nonce); PublicKey R = k.publicKey(); // flip k if r is not even if(!R.isEven()){ k = -k; R = -R; } // calculate hash using tagged hash with "BIP0340/challenge" prefix uint8_t e[32]; TaggedHash tch("BIP0340/challenge"); R.x(r, sizeof(r)); tch.write(r, sizeof(r)); pub.x(tmp, sizeof(tmp)); tch.write(tmp, sizeof(tmp)); tch.write(hash, 32); tch.end(e); PrivateKey challenge(e); // calculate s PrivateKey S = k + challenge*prv; S.getSecret(s); SchnorrSignature sig(r, s); return sig; } #if USE_ARDUINO_STRING || USE_STD_STRING PrivateKey::PrivateKey(const String wifString){ fromWIF(wifString.c_str()); } #else PrivateKey::PrivateKey(const char * wifArr){ fromWIF(wifArr); } #endif ================================================ FILE: src/Bitcoin.h ================================================ /** @file Bitcoin.h */ #ifndef __BITCOIN_H__ #define __BITCOIN_H__ #include "uBitcoin_conf.h" #include "BaseClasses.h" #include "BitcoinCurve.h" #include "Conversion.h" #include "Networks.h" #include "utility/trezor/rand.h" #include #include /* TODO: - autodetect mnemonic w/o passwd or xprv - HD.derive() - accept strings instead of char arrays for txout (address) and other things - fail if script or witness is too large - fix is_canonical function - refactor fromWIF to return bytes read - fix all warnings - psbt - docs - publish on arduino libs and mbed - operators +, += in script - concatenation - signature & everything from char array might be not a bright idea - tests (egde cases!) - sidechannel for pubkey calculation - use rng */ extern int ubtc_errno; // number of rounds for mnemonic to seed conversion #define PBKDF2_ROUNDS 2048 #define HARDENED_INDEX 0x80000000 /** \brief Common script types */ enum ScriptType{ UNKNOWN_TYPE, /** \brief a script directly in ScriptPubkey and not one of below */ DIRECT_SCRIPT, /** \brief default script for signing */ P2PKH, P2SH, P2WPKH, P2WSH, P2SH_P2WPKH, P2SH_P2WSH, MULTISIG }; /** \brief SigHash types */ enum SigHashType{ SIGHASH_ALL = 1, SIGHASH_NONE = 2, SIGHASH_SINGLE = 3 }; /* forward declarations */ class Signature; class SchnorrSignature; class PublicKey; class PrivateKey; class HDPublicKey; class HDPrivateKey; class Script; class TxIn; const char * generateMnemonic(uint8_t numWords); const char * generateMnemonic(uint8_t numWords, const uint8_t * entropy_data, size_t dataLen); const char * generateMnemonic(const uint8_t * entropy_data, size_t dataLen); #if USE_ARDUINO_STRING const char * generateMnemonic(uint8_t numWords, const String entropy_string); const char * generateMnemonic(const String entropy_string); bool checkMnemonic(const String mnemonic); #elif USE_STD_STRING const char * generateMnemonic(uint8_t numWords, const std::string entropy_string); const char * generateMnemonic(const std::string entropy_string); bool checkMnemonic(const std::string mnemonic); #else const char * generateMnemonic(uint8_t numWords, const char * entropy_string); const char * generateMnemonic(const char * entropy_string); bool checkMnemonic(const char * mnemonic); #endif const char * mnemonicFromEntropy(const uint8_t * entropy_data, size_t dataLen); size_t mnemonicToEntropy(const char * mnemonic, size_t mnemonic_len, uint8_t * output, size_t outputLen); #if USE_ARDUINO_STRING size_t mnemonicToEntropy(String mnemonic, uint8_t * output, size_t outputLen); #elif USE_STD_STRING size_t mnemonicToEntropy(std::string mnemonic, uint8_t * output, size_t outputLen); #else size_t mnemonicToEntropy(char * mnemonic, uint8_t * output, size_t outputLen); #endif /** * PublicKey class. * * Derived from ECPoint class, therefore you can add or substract them, multiply by ECScalar or PrivateKey. * * `compressed` flag determines what public key sec format to use by default: * - `compressed = false` will use 65-byte representation (`04`) * - `compressed = true` will use 33-byte representation (`03` if y is odd, `02` if y is even) */ class PublicKey : public ECPoint{ public: PublicKey(){ reset(); }; PublicKey(const uint8_t pubkeyArr[64], bool use_compressed){ reset(); memcpy(point, pubkeyArr, 64); compressed=use_compressed; }; PublicKey(const uint8_t * secArr){ reset(); parse(secArr, 33 + ((uint8_t)(secArr[0]==0x04))*32); }; explicit PublicKey(const char * secHex){ reset(); from_str(secHex, strlen(secHex)); }; PublicKey(const ECPoint p){ reset(); memcpy(point, p.point, 64); compressed=p.compressed; }; /** * \brief Fills `addr` with legacy Pay-To-Pubkey-Hash address (P2PKH, `1...` for mainnet) */ int legacyAddress(char * addr, size_t len, const Network * network = &DEFAULT_NETWORK) const; /** * \brief Fills `addr` with native segwit address (P2WPKH, `bc1...` for mainnet) */ int segwitAddress(char * addr, size_t len, const Network * network = &DEFAULT_NETWORK) const; /** * \brief Fills `addr` with nested segwit address (P2SH-P2WPKH, `3...` for mainnet) */ int nestedSegwitAddress(char * addr, size_t len, const Network * network = &DEFAULT_NETWORK) const; /** * \brief Alias for `legacyAddress` */ int address(char * address, size_t len, const Network * network = &DEFAULT_NETWORK) const{ return legacyAddress(address, len, network); }; #if USE_ARDUINO_STRING String legacyAddress(const Network * network = &DEFAULT_NETWORK) const; String segwitAddress(const Network * network = &DEFAULT_NETWORK) const; String nestedSegwitAddress(const Network * network = &DEFAULT_NETWORK) const; String address(const Network * network = &DEFAULT_NETWORK) const{ return legacyAddress(network); }; #endif #if USE_STD_STRING std::string legacyAddress(const Network * network = &DEFAULT_NETWORK) const; std::string segwitAddress(const Network * network = &DEFAULT_NETWORK) const; std::string nestedSegwitAddress(const Network * network = &DEFAULT_NETWORK) const; std::string address(const Network * network = &DEFAULT_NETWORK) const{ return legacyAddress(network); }; #endif /** * \brief verifies the ECDSA signature of the hash of the message */ bool verify(const Signature sig, const uint8_t hash[32]) const; /** * \brief verifies the Schnorr signature of the hash of the message */ bool schnorr_verify(const SchnorrSignature sig, const uint8_t hash[32]) const; /** * \brief Returns a Script with the type: `P2PKH`, `P2WPKH` or `P2SH_P2WPKH` */ Script script(ScriptType type = P2PKH) const; }; /** * PrivateKey class. * Corresponding public key (point on curve) will be calculated in the constructor. * as point calculation is pretty slow, class initialization can take some time. */ class PrivateKey : public ECScalar{ protected: /** \brief corresponding point on curve ( secret * G ) */ PublicKey pubKey; virtual size_t to_str(char * buf, size_t len) const{ return wif( buf, len); }; virtual size_t from_str(const char * buf, size_t len){ return fromWIF(buf, len); }; virtual size_t from_stream(ParseStream *s); public: PrivateKey(); PrivateKey(const uint8_t secret_arr[32], bool use_compressed = true, const Network * net = &DEFAULT_NETWORK); PrivateKey(const ECScalar sc); #if USE_ARDUINO_STRING PrivateKey(const String wifString); #elif USE_STD_STRING PrivateKey(const std::string wifString); #else PrivateKey(const char * wifArr); #endif ~PrivateKey(); /** \brief Length of the key in WIF format (52). In reality not always 52... */ virtual size_t stringLength() const{ return 52; }; virtual size_t length() const{ return 32; }; void setSecret(const uint8_t secret_arr[32]){ memcpy(num, secret_arr, 32); pubKey = *this * GeneratorPoint; }; /** \brief Pointer to the network to use. Mainnet or Testnet */ const Network * network; /** \brief Writes the private key in Wallet Import Format */ int wif(char * wifArr, size_t len) const; #if USE_ARDUINO_STRING String wif() const; #endif #if USE_STD_STRING std::string wif() const; #endif /** \brief Loads the private key from a string in Wallet Import Format */ int fromWIF(const char * wifArr, size_t wifSize); int fromWIF(const char * wifArr); /** \brief Returns the corresponding PublicKey = secret * GeneratorPoint */ PublicKey publicKey() const; /** \brief Signs the hash and returns the Signature */ Signature sign(const uint8_t hash[32]) const; // pass 32-byte hash of the message here /** \brief Signs the hash using Schnorr algorithm and returns the SchnorrSignature */ SchnorrSignature schnorr_sign(const uint8_t hash[32]) const; /** \brief Alias for .publicKey().address(network) */ int address(char * address, size_t len) const; /** \brief Alias for .publicKey().legacyAddress(network) */ int legacyAddress(char * address, size_t len) const; /** \brief Alias for .publicKey().segwitAddress(network) */ int segwitAddress(char * address, size_t len) const; /** \brief Alias for .publicKey().nestedSegwitAddress(network) */ int nestedSegwitAddress(char * address, size_t len) const; #if USE_ARDUINO_STRING String address() const; String legacyAddress() const; String segwitAddress() const; String nestedSegwitAddress() const; #endif #if USE_STD_STRING std::string address() const; std::string legacyAddress() const; std::string segwitAddress() const; std::string nestedSegwitAddress() const; #endif // PrivateKey &operator=(const PrivateKey &other); // assignment /** \brief Performs ECDH key agreement using public key of another party. * 32-byte shared secret will be written to `shared_secret` array. * Optional parameter hash (true by default) defines if you want sha256() or just . * Having hash=true is recommended unless you have a very good reason not to use it. */ int ecdh(const PublicKey pub, uint8_t shared_secret[32], bool hash=true); }; /** * \brief HD Private Key class. Derived from PrivateKey class. * Works according to [bip32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), * [bip39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) and * [slip32](https://github.com/satoshilabs/slips/blob/master/slip-0032.md). * You can generate the key from mnemonic or seed, derive children and hardened children. * xprv and xpub methods return strings according to slip32, xprv/xpub for bip44, yprv/ypub for bip49 and zprv/zpub for bip84 */ class HDPrivateKey : public PrivateKey{ protected: void init(); size_t to_bytes(uint8_t * arr, size_t len) const; virtual size_t to_str(char * buf, size_t len) const{ return xprv( buf, len); }; virtual size_t from_str(const char * buf, size_t len); virtual size_t from_stream(ParseStream *s); virtual size_t to_stream(SerializeStream *s, size_t offset = 0) const; uint8_t prefix[4]; // used for parsing only public: HDPrivateKey(); HDPrivateKey(const uint8_t secret[32], const uint8_t chain_code[32], uint8_t key_depth = 0, const uint8_t parent_fingerprint_arr[4] = NULL, uint32_t childnumber = 0, const Network * network = &DEFAULT_NETWORK, ScriptType key_type = UNKNOWN_TYPE); HDPrivateKey(const char xprvArr[]); HDPrivateKey(const char * mnemonic, size_t mnemonicSize, const char * password, size_t passwordSize, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); #if USE_STD_STRING HDPrivateKey(std::string mnemonic, std::string password, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); #endif #if USE_ARDUINO_STRING HDPrivateKey(String mnemonic, String password, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); #endif /* HDPrivateKey(const HDPrivateKey &other):HDPrivateKey( // copy other.num, other.chainCode, other.depth, other.parentFingerprint, other.childNumber, other.network, other.type){}; */ ~HDPrivateKey(); virtual size_t length() const{ return 78; }; /** \brief Length of the key in base58 encoding (111). */ virtual size_t stringLength() const{ return 111; }; uint8_t chainCode[32]; uint8_t depth; uint8_t parentFingerprint[4]; uint32_t childNumber; ScriptType type; int fromSeed(const uint8_t * seed, size_t seedSize, const Network * network = &DEFAULT_NETWORK); // int fromSeed(const uint8_t seed[64], const Network * network = &DEFAULT_NETWORK); int fromMnemonic(const char * mnemonic, size_t mnemonicSize, const char * password, size_t passwordSize, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); int fromMnemonic(const char * mnemonic, const char * password, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL){ return fromMnemonic(mnemonic, strlen(mnemonic), password, strlen(password), network, progress_callback); } #if USE_STD_STRING int fromMnemonic(std::string mnemonic, std::string password, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); #endif #if USE_ARDUINO_STRING int fromMnemonic(String mnemonic, String password, const Network * network = &DEFAULT_NETWORK, void (*progress_callback)(float) = NULL); #endif int xprv(char * arr, size_t len) const; int xpub(char * arr, size_t len) const; HDPublicKey xpub() const; int address(char * arr, size_t len) const; #if USE_ARDUINO_STRING String xprv() const; String address() const; #endif #if USE_STD_STRING std::string xprv() const; std::string address() const; #endif /** \brief populates array with the fingerprint of the key */ void fingerprint(uint8_t arr[4]) const; #if USE_STD_STRING std::string fingerprint() const; #endif #if USE_ARDUINO_STRING String fingerprint() const; #endif HDPrivateKey child(uint32_t index, bool hardened = false) const; HDPrivateKey hardenedChild(uint32_t index) const; /** \brief derives a child according to derivation path. Use 0x80000000 + index for hardened index. */ HDPrivateKey derive(uint32_t * index, size_t len) const; /** \brief derives a child according to derivation path. For example "m/84h/1h/0h/1/23/" for the 23rd change address for testnet with P2WPKH type (bip84). */ HDPrivateKey derive(const char * path) const; #if USE_ARDUINO_STRING HDPrivateKey derive(String path) const{ return derive(path.c_str()); }; #endif // just to make sure it is compressed PublicKey publicKey() const{ PublicKey p = pubKey; p.compressed = true; return p; }; // HDPrivateKey &operator=(const HDPrivateKey &other); // assignment }; /** * \brief HD Public Key class. Derived from PublicKey class. * Works according to [bip32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), * [bip39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) and * [slip32](https://github.com/satoshilabs/slips/blob/master/slip-0032.md). * You can derive children * xpub method return strings according to slip32, xpub for bip44, ypub for bip49 and zpub for bip84 */ class HDPublicKey : public PublicKey{ size_t to_bytes(uint8_t * arr, size_t len) const; virtual size_t to_str(char * buf, size_t len) const{ return xpub( buf, len); }; virtual size_t from_str(const char * buf, size_t len); virtual size_t from_stream(ParseStream *s); virtual size_t to_stream(SerializeStream *s, size_t offset = 0) const; uint8_t prefix[4]; // used for parsing only public: HDPublicKey(); HDPublicKey(const uint8_t point[64], const uint8_t chain_code[32], uint8_t key_depth = 0, const uint8_t parent_fingerprint_arr[4] = NULL, uint32_t childnumber = 0, const Network * net = &DEFAULT_NETWORK, ScriptType key_type = UNKNOWN_TYPE); HDPublicKey(const char * xpubArr); /* HDPublicKey(const HDPublicKey &other):HDPublicKey( // copy other.point, other.chainCode, other.depth, other.parentFingerprint, other.childNumber, other.network, other.type){}; */ #if USE_ARDUINO_STRING HDPublicKey(String pub){ reset(); from_str(pub.c_str(), pub.length()); }; #endif ~HDPublicKey(); /** \brief Length of the key (78). */ virtual size_t length() const{ return 78; }; /** \brief Length of the key in base58 encoding (111). */ virtual size_t stringLength() const{ return 111; }; uint8_t chainCode[32]; uint8_t depth; uint8_t parentFingerprint[4]; uint32_t childNumber; ScriptType type; const Network * network; int xpub(char * arr, size_t len) const; int address(char * arr, size_t len) const; #if USE_ARDUINO_STRING String xpub() const; String address() const; #endif #if USE_STD_STRING std::string xpub() const; std::string address() const; #endif /** \brief populates array with the fingerprint of the key */ void fingerprint(uint8_t arr[4]) const; #if USE_STD_STRING std::string fingerprint() const; #endif #if USE_ARDUINO_STRING String fingerprint() const; #endif /** \brief derive a child. * You can derive only normal children (not hardened) from the public key. */ HDPublicKey child(uint32_t index) const; /** \brief derives a child according to derivation path. */ HDPublicKey derive(uint32_t * index, size_t len) const; /** \brief derives a child according to derivation path. For example "m/1/23/" for the 23rd change address. */ HDPublicKey derive(const char * path) const; #if USE_ARDUINO_STRING HDPublicKey derive(String path) const{ return derive(path.c_str()); }; #endif // HDPublicKey &operator=(const HDPublicKey &other); // assignment }; /** * \brief Signature class. * Reference: https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki */ class Signature : public Streamable{ protected: uint8_t r[32]; uint8_t s[32]; virtual size_t from_stream(ParseStream *s); virtual size_t to_stream(SerializeStream *s, size_t offset = 0) const; size_t rlen() const; size_t slen() const; uint8_t tot[3]; // temporary thingy for parsing public: Signature(); Signature(const uint8_t r_arr[32], const uint8_t s_arr[32]); Signature(const uint8_t * der, size_t derLen); Signature(const uint8_t * der); explicit Signature(const char * der); virtual size_t length() const; uint8_t index; // used to derive pubkey from signature /** \brief encodes signature in der format and writes it to array */ size_t der(uint8_t * arr, size_t len) const; /** \brief parses signature in der format */ size_t fromDer(const uint8_t * arr, size_t len); /** \brief populates array with */ void bin(uint8_t * arr, size_t len) const; /** \brief parses array as */ void fromBin(const uint8_t * arr, size_t len); bool isValid() const{ uint8_t arr[32] = { 0 }; return !((memcmp(r, arr, 32) == 0) && (memcmp(s, arr, 32)==0)); }; explicit operator bool() const{ return isValid(); }; bool operator==(const Signature& other) const{ return (memcmp(r, other.r, 32) == 0) && (memcmp(s, other.s, 32) == 0); }; bool operator!=(const Signature& other) const{ return !operator==(other); }; }; /** * \brief SchnorrSignature class. * Reference: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki */ class SchnorrSignature : public Streamable{ protected: uint8_t r[32]; uint8_t s[32]; virtual size_t from_stream(ParseStream *s); virtual size_t to_stream(SerializeStream *s, size_t offset = 0) const; public: SchnorrSignature(); SchnorrSignature(const uint8_t r_arr[32], const uint8_t s_arr[32]); SchnorrSignature(const uint8_t rs_arr[64]); explicit SchnorrSignature(const char * rs); virtual size_t length() const{ return 64; }; bool isValid() const{ uint8_t arr[32] = { 0 }; return !((memcmp(r, arr, 32) == 0) && (memcmp(s, arr, 32)==0)); }; explicit operator bool() const{ return isValid(); }; bool operator==(const SchnorrSignature& other) const{ return (memcmp(r, other.r, 32) == 0) && (memcmp(s, other.s, 32) == 0); }; bool operator!=(const SchnorrSignature& other) const{ return !operator==(other); }; }; /** * \brief Script class. Parsing requires the length of the script in the beginning. */ class Script : public Streamable{ protected: virtual size_t from_stream(ParseStream *s); virtual size_t to_stream(SerializeStream *s, size_t offset = 0) const; uint8_t lenLen; // for parsing only, length of the varint void fromAddress(const char * address); void init(); public: uint8_t * scriptArray; size_t scriptLen; void clear(); Script(); Script(const uint8_t * buffer, size_t len); /** \brief creates a script from address */ Script(const char * address){ init(); fromAddress(address); }; #if USE_ARDUINO_STRING /** \brief creates a script from address */ Script(const String address){ init(); fromAddress(address.c_str()); }; #endif #if USE_STD_STRING /** \brief creates a script from address */ Script(const std::string address){ init(); fromAddress(address.c_str()); }; #endif /** \brief creates one of standart scripts (P2PKH, P2WPKH) */ Script(const PublicKey pubkey, ScriptType type = P2PKH); /** \brief creates one of standart scripts (P2SH, P2WSH) */ Script(const Script &other, ScriptType type); Script(const Script &other); // copy ~Script(){ if(scriptArray){ free(scriptArray); } }; /** \brief tries to determine the script type */ ScriptType type() const; /** \brief returns address corresponding to the script */ size_t address(char * buffer, size_t len, const Network * network = &DEFAULT_NETWORK) const; #if USE_ARDUINO_STRING String address(const Network * network = &DEFAULT_NETWORK) const; #endif #if USE_STD_STRING std::string address(const Network * network = &DEFAULT_NETWORK) const; #endif /** \brief length of the script with varint */ virtual size_t length() const; /** \brief pushes a single byte (op_code) to the end */ size_t push(uint8_t code); /** \brief pushes bytes from data object to the end */ size_t push(const uint8_t * data, size_t len); /** \brief adds to the script */ size_t push(const PublicKey pubkey); /** \brief adds to the script */ size_t push(const Signature sig, SigHashType sigType = SIGHASH_ALL); /** \brief adds