Skip to content

Commit 2d219e9

Browse files
feat: Taproot sign txn basic code
1 parent 6c4cd76 commit 2d219e9

File tree

3 files changed

+345
-4
lines changed

3 files changed

+345
-4
lines changed

apps/btc_family/btc_priv.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ typedef struct {
3434
uint8_t hash_outputs[32];
3535
} btc_segwit_cache_t;
3636

37+
typedef struct {
38+
bool filled;
39+
uint8_t sha_prevouts[32];
40+
uint8_t sha_amounts[32];
41+
uint8_t sha_scriptpubkeys[32];
42+
uint8_t sha_sequences[32];
43+
uint8_t sha_outputs[32];
44+
uint8_t sha_annex[32];
45+
uint8_t sha_single_output[32];
46+
} btc_taproot_cache_t;
47+
3748
typedef struct {
3849
pb_byte_t prev_txn_hash[32];
3950
uint32_t prev_output_index;
@@ -69,6 +80,8 @@ typedef struct {
6980
btc_sign_txn_metadata_t metadata;
7081
// Populated for segwit transactions
7182
btc_segwit_cache_t segwit_cache;
83+
// Populated for taproot transactions
84+
btc_taproot_cache_t taproot_cache;
7285

7386
/**
7487
* The structure holds the outputs (TxOut) of the transaction. Refer

apps/btc_family/btc_txn.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,8 @@ static bool sign_input(scrip_sig_t *signatures) {
669669

670670
// populate hashes cache for segwit transaction types
671671
btc_segwit_init_cache(btc_txn_context);
672+
// populate hashes cache for taproot transaction types
673+
btc_taproot_init_cache(btc_txn_context);
672674
if (!derive_hdnode_from_path(hd_path, 3, SECP256K1_NAME, buffer, &node) ||
673675
false == validate_change_address(&node)) {
674676
btc_send_error(ERROR_COMMON_ERROR_CORRUPT_DATA_TAG,
@@ -702,10 +704,15 @@ static bool sign_input(scrip_sig_t *signatures) {
702704
}
703705

704706
status = btc_digest_input(btc_txn_context, idx, buffer);
705-
ecdsa_sign_digest(
706-
curve, t_node.private_key, buffer, signatures[idx].bytes, NULL, NULL);
707-
signatures[idx].size = btc_sig_to_script_sig(
708-
signatures[idx].bytes, t_node.public_key, signatures[idx].bytes);
707+
if (TAPROOT_KEY_PATH == hd_path[0]) {
708+
schnorrsig_sign32(t_node.private_key, buffer, signatures[idx].bytes, NULL);
709+
}
710+
else {
711+
ecdsa_sign_digest(
712+
curve, t_node.private_key, buffer, signatures[idx].bytes, NULL, NULL);
713+
signatures[idx].size = btc_sig_to_script_sig(
714+
signatures[idx].bytes, t_node.public_key, signatures[idx].bytes);
715+
}
709716
if (0 == signatures[idx].size || false == status) {
710717
// early exit as digest could not be calculated
711718
btc_send_error(ERROR_COMMON_ERROR_UNKNOWN_ERROR_TAG, 1);

apps/btc_family/btc_txn_helpers.c

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,77 @@ void btc_segwit_init_cache(btc_txn_context_t *context) {
398398
memzero(&sha_256_ctx, sizeof(sha_256_ctx));
399399
}
400400

401+
void btc_taproot_init_cache(btc_txn_context_t *context) {
402+
btc_taproot_cache_t * taproot_cache = &context->taproot_cache;
403+
404+
//sha_prevouts
405+
uint8_t * prevouts_buf = malloc(36*context->metadata.input_count);
406+
memzero(prevouts_buf, 36*context->metadata.input_count);
407+
408+
for (uint32_t idx = 0, len = 0; idx < context->metadata.input_count; idx++) {
409+
memcpy(prevouts_buf+len, context->inputs[idx].prev_txn_hash, 32);
410+
len +=32;
411+
memcpy(prevouts_buf+len, (uint8_t*)&context->inputs[idx].prev_output_index, 4);
412+
len+4;
413+
}
414+
sha256_Raw(prevouts_buf, 36*context->metadata.input_count, taproot_cache->sha_prevouts);
415+
free(prevouts_buf);
416+
417+
//sha_amounts
418+
SHA256_CTX sha_256_ctx = {0};
419+
memzero(&sha_256_ctx, sizeof(sha_256_ctx));
420+
sha256_Init(&sha_256_ctx);
421+
422+
for (uint32_t idx = 0; idx < context->metadata.input_count; idx++) {
423+
sha256_Update(&sha_256_ctx, (uint8_t*)&context->inputs[idx].value, 8);
424+
}
425+
sha256_Final(&sha_256_ctx, taproot_cache->sha_amounts);
426+
427+
//sha_scriptpubkeys
428+
uint8_t * script_buf = malloc(35*context->metadata.input_count);
429+
memzero(script_buf, 35*context->metadata.input_count);
430+
431+
for (uint32_t idx = 0, len = 0; idx < context->metadata.input_count; idx++) {
432+
memcpy(script_buf + len, context->inputs[idx].script_pub_key.bytes, context->inputs[idx].script_pub_key.size);
433+
len += context->inputs[idx].script_pub_key.size;
434+
}
435+
sha256_Raw(script_buf, 35*context->metadata.input_count, taproot_cache->sha_scriptpubkeys);
436+
free(script_buf);
437+
438+
//sha_sequences
439+
sha256_Init(&sha_256_ctx);
440+
for (uint32_t idx = 0; idx < context->metadata.input_count; idx++) {
441+
sha256_Update(&sha_256_ctx, (uint8_t*)&context->inputs[idx].sequence, 4);
442+
}
443+
444+
sha256_Final(&sha_256_ctx, taproot_cache->sha_sequences);
445+
446+
//sha_outputs
447+
// calculate capacity
448+
uint32_t capacity = 0;
449+
uint32_t size = 0;
450+
while (size < context->metadata.output_count)
451+
{
452+
capacity +=8;
453+
capacity += context->outputs[size].script_pub_key.size;
454+
size++;
455+
}
456+
457+
uint8_t * output_buf = malloc(capacity);
458+
memzero(output_buf, capacity);
459+
460+
for (uint32_t idx = 0, len = 0; idx < context->metadata.output_count; idx++) {
461+
memcpy( output_buf+len, (uint8_t*)&context->outputs[idx].value, 8);
462+
len += 8;
463+
memcpy( output_buf+len, context->outputs[idx].script_pub_key.bytes, context->outputs[idx].script_pub_key.size);
464+
len += context->outputs[idx].script_pub_key.size;
465+
}
466+
sha256_Raw(output_buf, capacity, taproot_cache->sha_outputs);
467+
free(output_buf);
468+
469+
taproot_cache->filled = true;
470+
}
471+
401472
bool btc_digest_input(const btc_txn_context_t *context,
402473
const uint32_t index,
403474
uint8_t *digest) {
@@ -412,8 +483,258 @@ bool btc_digest_input(const btc_txn_context_t *context,
412483
} else if (SCRIPT_TYPE_P2PKH == type) {
413484
// p2pkh digest calculation; has not failure case
414485
calculate_p2pkh_digest(context, index, digest);
486+
} else if (SCRIPT_TYPE_P2SH == type) {
487+
status = calculate_p2wpkh_in_p2sh_digest(context, index, digest);
488+
} else if (SCRIPT_TYPE_P2TR == type) {
489+
status = calculate_p2tr_digest(context, index, digest);
415490
} else {
416491
status = false;
417492
}
418493
return status;
419494
}
495+
496+
#include "ecdsa.h"
497+
#include "secp256k1.h"
498+
#include "memzero.h"
499+
500+
typedef struct {
501+
uint8_t secret_key[32]; // Private key
502+
uint8_t public_key[32]; // X-coordinate of public key (for BIP340)
503+
uint8_t has_even_y; // Whether Y-coordinate is even
504+
} bip340_keypair_t;
505+
506+
int keypair_create(bip340_keypair_t *keypair, const uint8_t *private_key_bytes) {
507+
// 1. Validate private key (must be in range [1, n-1])
508+
bignum256 sk;
509+
bn_read_be(private_key_bytes, &sk);
510+
511+
// Check if sk is valid (not zero and less than curve order)
512+
if (bn_is_zero(&sk)) {
513+
return -1;
514+
}
515+
516+
if (!bn_is_less(&sk, &secp256k1.order)) {
517+
return -1;
518+
}
519+
520+
// 2. Copy private key
521+
memcpy(keypair->secret_key, private_key_bytes, 32);
522+
523+
// 3. Calculate public key: P = sk * G
524+
curve_point pub;
525+
scalar_multiply(&secp256k1, &sk, &pub);
526+
527+
// 4. Convert to affine coordinates and get x-coordinate
528+
bignum256 x, y;
529+
bn_inverse(&pub.z, &secp256k1.prime);
530+
bignum256 z_squared;
531+
bn_multiply(&pub.z, &pub.z, &secp256k1.prime);
532+
bn_multiply(&pub.x, &z_squared, &secp256k1.prime);
533+
bn_mod(&pub.x, &secp256k1.prime);
534+
535+
// Store x-coordinate
536+
bn_write_be(&pub.x, keypair->public_key);
537+
538+
// 5. Check if Y is even (needed for BIP340)
539+
bn_multiply(&pub.z, &z_squared, &secp256k1.prime);
540+
bn_multiply(&pub.y, &pub.z, &secp256k1.prime);
541+
bn_mod(&pub.y, &secp256k1.prime);
542+
keypair->has_even_y = !bn_is_odd(&pub.y);
543+
544+
// Clear sensitive data
545+
memzero(&sk, sizeof(sk));
546+
memzero(&pub, sizeof(pub));
547+
548+
return 0;
549+
}
550+
551+
#include "hasher.h"
552+
#include "hmac.h"
553+
554+
// BIP340 tagged hash implementation
555+
void bip340_tagged_hash(const char *tag, uint8_t *out,
556+
const uint8_t *data, size_t data_len) {
557+
uint8_t tag_hash[32];
558+
Hasher hasher;
559+
560+
// Hash the tag
561+
hasher_Init(&hasher, HASHER_SHA2);
562+
hasher_Update(&hasher, (const uint8_t*)tag, strlen(tag));
563+
hasher_Final(&hasher, tag_hash);
564+
565+
// tagged_hash = SHA256(SHA256(tag) || SHA256(tag) || data)
566+
hasher_Init(&hasher, HASHER_SHA2);
567+
hasher_Update(&hasher, tag_hash, 32);
568+
hasher_Update(&hasher, tag_hash, 32);
569+
hasher_Update(&hasher, data, data_len);
570+
hasher_Final(&hasher, out);
571+
}
572+
573+
int schnorrsig_sign32(uint8_t *signature_bytes,
574+
const uint8_t *digest,
575+
const bip340_keypair_t *keypair,
576+
const uint8_t *auxiliary_data) {
577+
bignum256 sk, e, k, r_x;
578+
curve_point R;
579+
uint8_t aux[32];
580+
uint8_t nonce_data[32 + 32 + 32 + 32]; // sk || P.x || msg || aux
581+
uint8_t nonce_hash[32];
582+
583+
// 1. Load private key
584+
bn_read_be(keypair->secret_key, &sk);
585+
586+
// 2. Adjust private key if public key has odd Y
587+
// BIP340 requires: if has_odd_y(P), then sk = n - sk
588+
if (!keypair->has_even_y) {
589+
bn_subtract(&secp256k1.order, &sk, &sk);
590+
bn_mod(&sk, &secp256k1.order);
591+
}
592+
593+
// 3. Prepare auxiliary data (use zeros if NULL)
594+
if (auxiliary_data == NULL) {
595+
memzero(aux, 32);
596+
} else {
597+
memcpy(aux, auxiliary_data, 32);
598+
}
599+
600+
// 4. Generate deterministic nonce k (BIP340)
601+
// k = tagged_hash("BIP0340/nonce", sk || P.x || msg || aux)
602+
603+
// First, XOR auxiliary data with private key for additional randomness
604+
uint8_t sk_bytes[32];
605+
bn_write_be(&sk, sk_bytes);
606+
for (int i = 0; i < 32; i++) {
607+
sk_bytes[i] ^= aux[i];
608+
}
609+
610+
// Build nonce input: sk || P.x || msg || aux
611+
memcpy(nonce_data, sk_bytes, 32);
612+
memcpy(nonce_data + 32, keypair->public_key, 32);
613+
memcpy(nonce_data + 64, digest, 32);
614+
memcpy(nonce_data + 96, aux, 32);
615+
616+
// Generate nonce
617+
bip340_tagged_hash("BIP0340/nonce", nonce_hash, nonce_data, 128);
618+
bn_read_be(nonce_hash, &k);
619+
bn_mod(&k, &secp256k1.order);
620+
621+
// Ensure k != 0
622+
if (bn_is_zero(&k)) {
623+
return -1;
624+
}
625+
626+
// 5. Calculate R = k*G
627+
scalar_multiply(&secp256k1, &k, &R);
628+
629+
// 6. Convert R to affine coordinates and get x-coordinate
630+
bignum256 z_inv, z_inv_squared;
631+
bn_inverse(&R.z, &secp256k1.prime);
632+
bn_multiply(&z_inv, &z_inv, &secp256k1.prime);
633+
bn_multiply(&R.x, &z_inv, &secp256k1.prime);
634+
bn_mod(&R.x, &secp256k1.prime);
635+
636+
// Also get R.y for parity check
637+
bn_multiply(&z_inv, &z_inv_squared, &secp256k1.prime);
638+
bn_multiply(&R.y, &z_inv, &secp256k1.prime);
639+
bn_mod(&R.y, &secp256k1.prime);
640+
641+
// 7. If R.y is odd, negate k
642+
if (bn_is_odd(&R.y)) {
643+
bn_subtract(&secp256k1.order, &k, &k);
644+
bn_mod(&k, &secp256k1.order);
645+
}
646+
647+
// 8. Store R.x in signature (first 32 bytes)
648+
bn_write_be(&R.x, signature_bytes);
649+
650+
// 9. Calculate e = tagged_hash("BIP0340/challenge", R.x || P.x || m)
651+
uint8_t challenge_data[32 + 32 + 32]; // R.x || P.x || msg
652+
uint8_t challenge_hash[32];
653+
654+
// Build challenge input: R.x || P.x || msg
655+
memcpy(challenge_data, signature_bytes, 32); // R.x (already stored)
656+
memcpy(challenge_data + 32, keypair->public_key, 32); // P.x
657+
memcpy(challenge_data + 64, digest, 32); // message
658+
659+
// Generate challenge e
660+
bip340_tagged_hash("BIP0340/challenge", challenge_hash, challenge_data, 96);
661+
bn_read_be(challenge_hash, &e);
662+
bn_mod(&e, &secp256k1.order);
663+
664+
// 10. Calculate s = k + e*sk mod n
665+
bignum256 s, temp;
666+
bn_multiply(&e, &sk, &secp256k1.order); // temp = e*sk mod n
667+
bn_copy(&e, &temp);
668+
bn_addmod(&k, &temp, &secp256k1.order); // s = k + e*sk mod n
669+
bn_copy(&k, &s);
670+
671+
// 11. Store s in signature (second 32 bytes)
672+
bn_write_be(&s, signature_bytes + 32);
673+
674+
// 12. Verify signature is valid (optional but recommended)
675+
// This catches any implementation errors
676+
uint8_t verify_result[32];
677+
if (manual_schnorrsig_verify(signature_bytes, digest, keypair->public_key) !=
678+
0) {
679+
memzero(signature_bytes, 64);
680+
return -1;
681+
}
682+
683+
// Clear all sensitive data
684+
memzero(&sk, sizeof(sk));
685+
memzero(&k, sizeof(k));
686+
memzero(&e, sizeof(e));
687+
memzero(&s, sizeof(s));
688+
memzero(&R, sizeof(R));
689+
memzero(sk_bytes, sizeof(sk_bytes));
690+
memzero(nonce_data, sizeof(nonce_data));
691+
memzero(nonce_hash, sizeof(nonce_hash));
692+
memzero(challenge_data, sizeof(challenge_data));
693+
memzero(challenge_hash, sizeof(challenge_hash));
694+
695+
return 0;
696+
}
697+
698+
// Helper function for verification (optional)
699+
int manual_schnorrsig_verify(const uint8_t *signature,
700+
const uint8_t *digest,
701+
const uint8_t *public_key_x) {
702+
bignum256 r, s, e, x;
703+
curve_point R, P, sG, eP;
704+
705+
// Load r and s from signature
706+
bn_read_be(signature, &r);
707+
bn_read_be(signature + 32, &s);
708+
709+
// Check r and s are in valid range
710+
if (!bn_is_less(&r, &secp256k1.prime) || !bn_is_less(&s, &secp256k1.order)) {
711+
return -1;
712+
}
713+
714+
// Load public key x-coordinate
715+
bn_read_be(public_key_x, &x);
716+
717+
// Compute e = tagged_hash("BIP0340/challenge", r || P.x || m)
718+
uint8_t challenge_data[96];
719+
uint8_t challenge_hash[32];
720+
memcpy(challenge_data, signature, 32); // r
721+
memcpy(challenge_data + 32, public_key_x, 32); // P.x
722+
memcpy(challenge_data + 64, digest, 32); // message
723+
724+
bip340_tagged_hash("BIP0340/challenge", challenge_hash, challenge_data, 96);
725+
bn_read_be(challenge_hash, &e);
726+
bn_mod(&e, &secp256k1.order);
727+
728+
// Recover P from x-coordinate (lift_x)
729+
if (point_lift_x(&secp256k1, &P, &x) != 0) {
730+
return -1;
731+
}
732+
733+
// Compute R = s*G - e*P
734+
scalar_multiply(&secp256k1, &s, &sG); // s*G
735+
scalar_multiply(&secp256k1, &e, &eP); // e*P (using recovered P)
736+
point_multiply(&secp256k1, &e, &P, &eP);
737+
738+
// Negate eP
739+
// bn_subtract(&secp256k1)
740+
}

0 commit comments

Comments
 (0)