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