Skip to content

Commit 4a7c119

Browse files
committed
Satochip-Applet v0.14-0.2: Schnorr signature - add option to bypass key tweaking (beta)
Some schemes like Taproot require key tweaking, others (like Nostr) don't.
1 parent a43d255 commit 4a7c119

File tree

1 file changed

+62
-47
lines changed

1 file changed

+62
-47
lines changed

src/org/satochip/applet/CardEdge.java

+62-47
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,11 @@ public class CardEdge extends javacard.framework.Applet {
104104
// 0.12-0.4: add reset to factory support
105105
// 0.12-0.5: add support for personalisation PKI
106106
// 0.14-0.1: add Schnorr signature support (beta)
107+
// 0.14-0.2: Schnorr signature - add option to bypass key tweaking (beta)
107108
private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0;
108109
private final static byte PROTOCOL_MINOR_VERSION = (byte) 14;
109110
private final static byte APPLET_MAJOR_VERSION = (byte) 0;
110-
private final static byte APPLET_MINOR_VERSION = (byte) 1;
111+
private final static byte APPLET_MINOR_VERSION = (byte) 2;
111112

112113
// Maximum number of keys handled by the Cardlet
113114
private final static byte MAX_NUM_KEYS = (byte) 16;
@@ -2475,14 +2476,14 @@ private short SignTransactionHash(APDU apdu, byte[] buffer){
24752476
* A private key must first be available, either from a keyslot or
24762477
* derived from a BIP32 seed using getBIP32ExtendedKey().
24772478
*
2478-
* The tweaked key is then stored in the same keyslot and available next for signing.
2479+
* The tweaked key is then stored in a dedicated keyslot and available next for schnorr signing.
24792480
* The function returns the corresponding public key coordx, self-signed
24802481
*
2481-
* TODO: allows to bypass tweaking by providing empty tweak?
2482+
* If P2 parameter is not 0x00, the tweaking is bypassed.
24822483
*
24832484
* ins: 0x7C
24842485
* p1: key number or 0xFF for the last derived Bip32 extended key
2485-
* p2: 0x00
2486+
* p2: 0x00 for key tweak, 0x01 to bypass tweak
24862487
* data: [tweak_size (1b) | tweak data (32b)]
24872488
*
24882489
* return: [coordx_size(2b) | coordx | sig_size(2b) | authentikey_sig]
@@ -2498,17 +2499,13 @@ private short TaprootTweakPrivateKey(APDU apdu, byte[] buffer){
24982499
if ( (key_nb!=(byte)0xFF) && ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) )
24992500
ISOException.throwIt(SW_INCORRECT_P1);
25002501

2502+
// P2 can be used to bypass tweaking and copy private as is
2503+
byte p2 = buffer[ISO7816.OFFSET_P2];
2504+
25012505
// check whether the seed is initialized
25022506
if (key_nb==(byte)0xFF && !bip32_seeded)
25032507
ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
25042508

2505-
// tweak vector should be exactly 32bytes, thus 33 bytes in total
2506-
short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
2507-
if (bytesLeft< (byte)33)
2508-
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2509-
if (buffer[ISO7816.OFFSET_CDATA] != (byte)32)
2510-
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2511-
25122509
// get privkey from either bip32 derivation or single privkey
25132510
ECPrivateKey privkey;
25142511
if (key_nb==(byte)0xFF)
@@ -2524,47 +2521,65 @@ private short TaprootTweakPrivateKey(APDU apdu, byte[] buffer){
25242521
ISOException.throwIt(SW_INCORRECT_ALG);
25252522
}
25262523

2527-
// compute pubkey corresponding to private key
2528-
keyAgreement.init((ECPrivateKey)privkey);
2529-
keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); //pubkey in uncompressed form (65bytes)
2524+
short seckeyOffset;
2525+
short buffer_offset;
2526+
if (p2==(byte)0x00){
25302527

2531-
// xor pubkey coordx with tweak vector
2532-
short recvBuffer_offset = (short)0x1;
2533-
short buffer_offset = (short)(ISO7816.OFFSET_CDATA+1);
2534-
short i;
2535-
for (i = (short)0; i < (short)32; i++){
2536-
recvBuffer[(short)(recvBuffer_offset+i)] = (byte) (recvBuffer[(short)(recvBuffer_offset+i)] ^ buffer[(short)(buffer_offset+i)]);
2537-
}
2528+
// tweak vector should be exactly 32bytes, thus 33 bytes in total
2529+
short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
2530+
if (bytesLeft< (byte)33)
2531+
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2532+
if (buffer[ISO7816.OFFSET_CDATA] != (byte)32)
2533+
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
25382534

2539-
// compute tagged hash
2540-
// t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
2541-
schnorr_hash_tag(tags, (short)41, (short)8, recvBuffer, recvBuffer_offset, (short)32, recvBuffer, (short)65);
2542-
// fails if t >= SECP256K1_ORDER (low probability)
2543-
if(!Biginteger.lessThan(recvBuffer, (short)65, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32)){
2544-
ISOException.throwIt(SW_TAPROOT_TWEAK_ERROR);
2545-
}
25462535

2547-
//seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
2548-
short seckeyOffset = (short) 97;
2549-
if ( (recvBuffer[(byte)64] % 0x02) == 0x00) {
2550-
privkey.getS(recvBuffer, seckeyOffset);
2551-
} else {
2552-
privkey.getS(recvBuffer, (short)129);
2553-
// copy SECP256K1_ORDER
2554-
Util.arrayCopyNonAtomic(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, recvBuffer, seckeyOffset, (short)32);
2555-
// SECP256K1_ORDER - seckey0
2556-
Biginteger.subtract(recvBuffer, seckeyOffset, recvBuffer, (short)129, (short) 32);
2557-
}
2536+
// compute pubkey corresponding to private key
2537+
keyAgreement.init((ECPrivateKey)privkey);
2538+
keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, (short)0); //pubkey in uncompressed form (65bytes)
25582539

2559-
// compute ((seckey + t) % SECP256K1_ORDER)
2560-
if (Biginteger.add_carry(recvBuffer, seckeyOffset, recvBuffer, (short)65, (short) 32)){
2561-
// in case of final carry, we must substract SECP256K1_R
2562-
Biginteger.subtract(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32);
2563-
} else {
2564-
// in the unlikely case where SECP256K1_R <= (seckey + t) <2^256
2565-
if(!Biginteger.lessThan(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32)){
2566-
Biginteger.subtract(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32);
2540+
// xor pubkey coordx with tweak vector
2541+
short recvBuffer_offset = (short)0x1;
2542+
buffer_offset = (short)(ISO7816.OFFSET_CDATA+1);
2543+
short i;
2544+
for (i = (short)0; i < (short)32; i++){
2545+
recvBuffer[(short)(recvBuffer_offset+i)] = (byte) (recvBuffer[(short)(recvBuffer_offset+i)] ^ buffer[(short)(buffer_offset+i)]);
2546+
}
2547+
2548+
// compute tagged hash
2549+
// t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h))
2550+
schnorr_hash_tag(tags, (short)41, (short)8, recvBuffer, recvBuffer_offset, (short)32, recvBuffer, (short)65);
2551+
// fails if t >= SECP256K1_ORDER (low probability)
2552+
if(!Biginteger.lessThan(recvBuffer, (short)65, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32)){
2553+
ISOException.throwIt(SW_TAPROOT_TWEAK_ERROR);
2554+
}
2555+
2556+
//seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
2557+
seckeyOffset = (short) 97;
2558+
if ( (recvBuffer[(byte)64] % 0x02) == 0x00) {
2559+
privkey.getS(recvBuffer, seckeyOffset);
2560+
} else {
2561+
privkey.getS(recvBuffer, (short)129);
2562+
// copy SECP256K1_ORDER
2563+
Util.arrayCopyNonAtomic(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, recvBuffer, seckeyOffset, (short)32);
2564+
// SECP256K1_ORDER - seckey0
2565+
Biginteger.subtract(recvBuffer, seckeyOffset, recvBuffer, (short)129, (short) 32);
25672566
}
2567+
2568+
// compute ((seckey + t) % SECP256K1_ORDER)
2569+
if (Biginteger.add_carry(recvBuffer, seckeyOffset, recvBuffer, (short)65, (short) 32)){
2570+
// in case of final carry, we must substract SECP256K1_R
2571+
Biginteger.subtract(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32);
2572+
} else {
2573+
// in the unlikely case where SECP256K1_R <= (seckey + t) <2^256
2574+
if(!Biginteger.lessThan(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32)){
2575+
Biginteger.subtract(recvBuffer, seckeyOffset, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, (short)32);
2576+
}
2577+
}
2578+
2579+
} else {
2580+
// just copy privkey as is
2581+
seckeyOffset = (short) 97;
2582+
privkey.getS(recvBuffer, seckeyOffset);
25682583
}
25692584

25702585
// save tweaked secret key

0 commit comments

Comments
 (0)