Coinkite Tap Protocol implemented in C++.
$ cd your_project/
$ git submodule add https://github.com/nunchuk-io/tap-protocol
$ git submodule update --init --recursive
Add the following to your CMakeLists.txt
.
add_subdirectory(tap-protocol)
target_link_libraries("${PROJECT_NAME}" PUBLIC tap-protocol)
Build secp256k1.
# Android
$ ./tool/build_android.sh
# iOS
$ PLATFORM_NAME=iphoneos CONFIGURATION=debug ARCHS=arm64 ./tools/build_ios.sh
# Add these libs into XCode project Build Phases
# build/contrib/bitcoin-core/libbitcoin-core.a
# build/contrib/bitcoin-core/src/secp256k1/build/iphoneos/libsecp256k1.a
# Linux
$ ./tool/build_linux.sh
TAPSIGNER
using namespace tap_protocol;
// First create a transport that sends bytes to the device
auto transport = MakeDefaultTransport([](const Bytes& bytes) {
// see how to send bytes to NFC card on Android or iOS below
});
// Create a Tapsigner using transport
auto tapsigner = std::make_unique<Tapsigner>(std::move(transport));
// or simply:
// Tapsigner tapsigner(std::move(transport));
// Get card status
auto status = tapsigner->Status();
if (tapsigner->NeedSetup()) {
// Set up a new card
Bytes chain_code = RandomChainCode(); // generate random chain code
std::string cvc = "123456";
auto setup = tapsigner->New(chain_code, cvc);
}
// See more commands in include/tap_protocol/cktapcard.h
Alternatively, we can use Tapsigner HWI interface
// Create HWI-like interface
auto hwi = MakeHWITapsigner(tapsigner.get(), "123456");
// Set up a new card
hwi->SetupDevice();
// Card fingerprint
std::string fingerprint = hwi->GetMasterFingerprint();
// Sign message
std::string signed_message = hwi->SignMessage("nunchuk", "m/84h/0h/0h");
// Sign transaction
std::string base64_psbt = "...";
std::string signed_tx = hwi->SignTx(base64_psbt);
// Backup-restore
auto bytes = hwi->BackupDevice();
std::string decrypt_backup = hwi->DecryptBackup(bytes, "backup key");
SATSCARD
// First create a transport that sends bytes to the device
auto transport = MakeDefaultTransport([](const Bytes& bytes) {
// see how to send bytes to NFC card for Android or iOS below
});
auto satscard = Satscard(std::move(transport));
if (satscard.IsUsedUp()) {
// Card is used up
// but let's check just in case some BTC are still in there
// Get all unsealed slots
std::vector<Satscard::Slot> slots = satscard.ListSlots();
for (const auto &slot : slots) {
// checking each slot address
auto balance = SOME_CHECKING_BALANCE_API(slot.address);
// Obtain private key
auto slot_with_priv = satscard.GetSlot(slot.index, "123456");
// Get wif of slot
std::string wif = slot_with_priv.to_wif(satscard.IsTestnet());
// Import wif then sweep func
}
// Or we can get all private key info in one go
std::vector<Satscard::Slot> slots_with_privkey = satscard.ListSlots("123456");
for (const auto &slot : slots_with_privkey) {
std::string wif = slot.to_wif();
}
return;
}
// Set up a new slot
if (satscard.NeedSetup()) {
// Setup for slot # satscard.GetActiveSlotIndex()
Bytes chain_code = RandomChainCode(); // generate random chain code
std::string cvc = "123456";
auto resp = satscard.New(chain_code, cvc);
// slot address to deposit
std::string address = resp.address;
} else {
// Current slot is sealed so we can deposit to this address
std::string address = slot.address;
// Unseal & sweep the func
std::string cvc = "123456";
auto unseal = satscard.Unseal(cvc);
// get private key for this slot
Bytes privkey = unseal.privkey;
// in WIF format
std::string wif = unseal.to_wif(satscard.IsTestnet());
}
Check if card is TAPSIGNER or SATSCARD
CKTapCard card(std::move(tp));
if (card.IsTapsigner()) {
auto tapsigner = ToTapsigner(std::move(card));
// Do command with tapsigner
} else {
auto satscard = ToSatscard(std::move(card));
// Do command with satscard
}
Android Intent that handles NFC events
protected void onNewIntent(Intent intent) {
// ...
byte[] id = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID);
Tag tag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
IsoDep card = IsoDep.get(tag);
try {
card.connect();
cardStatus(card);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (card != null) {
card.close()
}
}
}
static {
System.loadLibrary("<your-library-name>");
}
public native void cardStatus(IsoDep card);
JNI code
#include <tap_protocol/tap_protocol.h>
#include <tap_protocol/cktapcard.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_example_tap_1protocol_1nativesdk_MainActivity_cardStatus(JNIEnv *env, jobject thiz,
jobject card) {
auto transport = tap_protocol::MakeDefaultTransport([=](const tap_protocol::Bytes &in) {
jclass isoDepClass = env->FindClass("android/nfc/tech/IsoDep");
jmethodID tranceiveMethodID = env->GetMethodID(isoDepClass, "transceive", "([B)[B");
auto bytesToSend = env->NewByteArray(in.size());
env->SetByteArrayRegion(bytesToSend, 0, in.size(), (jbyte *) in.data());
auto bytesReceive = (jbyteArray) env->CallObjectMethod(card, tranceiveMethodID, bytesToSend);
env->DeleteLocalRef(bytesToSend);
auto firstByte = env->GetByteArrayElements(bytesReceive, JNI_FALSE);
tap_protocol::Bytes result((char *) firstByte, (char *) firstByte + env->GetArrayLength(bytesReceive));
env->ReleaseByteArrayElements(bytesReceive, firstByte, JNI_ABORT);
return result;
});
tap_protocol::Tapsigner tapsigner(std::move(transport));
// Run command `status`
auto status = tapsigner.Status();
}
#include <tap_protocol/tap_protocol.h>
#include <tap_protocol/cktapcard.h>
using namespace tap_protocol;
@interface YourLibName () <NFCTagReaderSessionDelegate>
dispatch_queue_t nfcQueue = dispatch_queue_create([@"io.nunchuk.nfc" UTF8String], dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, -1));
dispatch_semaphore_t semaphore;
- (void) cardStatus {
NFCTagReaderSession *session = [[NFCTagReaderSession alloc] initWithPollingOption:NFCPollingISO14443 | NFCPollingISO15693 | NFCPollingISO18092 delegate:self queue:nfcQueue];
[session beginSession];
semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
id<NFCTag> tag = session.connectedTag;
auto transport = MakeDefaultTransportIOS([tag](const APDURequest &req) {
Bytes bytes = {req.cla, req.ins, req.p1, req.p2};
NSMutableData *data = [[NSMutableData alloc] initWithBytes:bytes.data() length:bytes.size() * sizeof(unsigned char)];
[data appendBytes:req.data.data() length:req.data.size() * sizeof(unsigned char)];
NFCISO7816APDU *apdu = [[NFCISO7816APDU alloc] initWithData:data];
__block auto response = APDUResponse();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[tag.asNFCISO7816Tag sendCommandAPDU:apdu completionHandler:^(NSData * _Nonnull responseData, uint8_t sw1, uint8_t sw2, NSError * _Nullable error) {
const unsigned char *dataArray = (unsigned char *)responseData.bytes;
const size_t count = responseData.length / sizeof(unsigned char);
response.data = Bytes(dataArray, dataArray + count);;
response.sw1 = sw1;
response.sw2 = sw2;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return response;
});
Tapsigner tapsigner(std::move(transport));
// Run command `status`
auto status = tapsigner.Status();
[session invalidateSession];
}
- (void)tagReaderSession:(NFCTagReaderSession *)session didDetectTags:(NSArray<__kindof id<NFCTag>> *)tags {
id<NFCTag> tag = [tags firstObject];
[session connectToTag:tag completionHandler:^(NSError * _Nullable error) {
if (error != NULL) {
[session invalidateSessionWithErrorMessage:@"Unable to connect tag"];
return;
}
dispatch_semaphore_signal(semaphore);
}];
}
See https://github.com/coinkite/coinkite-tap-proto/tree/master/emulator
mkdir build
cd build
cmake -DBUILD_TESTING=ON ..
make all test -j4