Skip to content

Commit 852beaf

Browse files
committed
keystore: pass private key to nonce_commit() and sign()
End-goal: reduce the number of secure chip ops when signing a BTC transaction, to reduce the chance of going over the Optiga chip's "rate limit", which induces throttling. With antiklepto, we derived the private key twice for each input that is signed: once to commit to the nonce, and after that to sign. This commit decouples the nonce commit and sign functions from the underlying keystore, and allows reusing a private key instead of re-deriving it, which requires secure chip operations. This halves the number of secure chip ops needed per input when signing a BTC transaction. We do not reuse the privkey for the other instances of antiklepto (signing a msg, signing an ETH tx), as there it's one commit/sign pair only and unlikely to cause secure chip throttling.
1 parent c0b87ff commit 852beaf

File tree

9 files changed

+81
-67
lines changed

9 files changed

+81
-67
lines changed

src/keystore.c

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -528,19 +528,11 @@ bool keystore_get_bip39_word(uint16_t idx, char** word_out)
528528
}
529529

530530
bool keystore_secp256k1_nonce_commit(
531-
const uint32_t* keypath,
532-
size_t keypath_len,
531+
const uint8_t* private_key,
533532
const uint8_t* msg32,
534533
const uint8_t* host_commitment,
535534
uint8_t* signer_commitment_out)
536535
{
537-
uint8_t private_key[32] = {0};
538-
UTIL_CLEANUP_32(private_key);
539-
if (!rust_secp256k1_get_private_key(
540-
keypath, keypath_len, rust_util_bytes_mut(private_key, sizeof(private_key)))) {
541-
return false;
542-
}
543-
544536
const secp256k1_context* ctx = wally_get_secp_context();
545537
secp256k1_ecdsa_s2c_opening signer_commitment;
546538
if (!secp256k1_ecdsa_anti_exfil_signer_commit(
@@ -555,23 +547,12 @@ bool keystore_secp256k1_nonce_commit(
555547
}
556548

557549
bool keystore_secp256k1_sign(
558-
const uint32_t* keypath,
559-
size_t keypath_len,
550+
const uint8_t* private_key,
560551
const uint8_t* msg32,
561552
const uint8_t* host_nonce32,
562553
uint8_t* sig_compact_out,
563554
int* recid_out)
564555
{
565-
if (keystore_is_locked()) {
566-
return false;
567-
}
568-
uint8_t private_key[32] = {0};
569-
UTIL_CLEANUP_32(private_key);
570-
if (!rust_secp256k1_get_private_key(
571-
keypath, keypath_len, rust_util_bytes_mut(private_key, sizeof(private_key)))) {
572-
return false;
573-
}
574-
575556
const secp256k1_context* ctx = wally_get_secp_context();
576557
secp256k1_ecdsa_signature secp256k1_sig = {0};
577558
if (!secp256k1_anti_exfil_sign(

src/keystore.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,24 +157,22 @@ USE_RESULT bool keystore_get_bip39_word(uint16_t idx, char** word_out);
157157
* Get a commitment to the original nonce before tweaking it with the host nonce. This is part of
158158
* the ECDSA Anti-Klepto Protocol. For more details, check the docs of
159159
* `secp256k1_ecdsa_anti_exfil_signer_commit`.
160-
* @param[in] keypath derivation keypath
161-
* @param[in] keypath_len size of keypath buffer
160+
* @param[in] private_key 32 byte private key
162161
* @param[in] msg32 32 byte message which will be signed by `keystore_secp256k1_sign`.
163162
* @param[in] host_commitment must be `sha256(sha256(tag)||shas256(tag)||host_nonce)` where
164163
* host_nonce is passed to `keystore_secp256k1_sign()`. See
165164
* `secp256k1_ecdsa_anti_exfil_host_commit()`.
166165
* @param[out] client_commitment_out EC_PUBLIC_KEY_LEN bytes compressed signer nonce pubkey.
167166
*/
168167
USE_RESULT bool keystore_secp256k1_nonce_commit(
169-
const uint32_t* keypath,
170-
size_t keypath_len,
168+
const uint8_t* private_key,
171169
const uint8_t* msg32,
172170
const uint8_t* host_commitment,
173171
uint8_t* client_commitment_out);
174172

175173
// clang-format off
176174
/**
177-
* Sign message with private key at the given keypath. Keystore must be unlocked.
175+
* Sign message with private key using the given private key.
178176
*
179177
* Details about `host_nonce32`, the host nonce contribution.
180178
* Instead of using plain rfc6979 to generate the nonce in this signature, the following formula is used:
@@ -187,8 +185,7 @@ USE_RESULT bool keystore_secp256k1_nonce_commit(
187185
* This is part of the ECSDA Anti-Klepto protocol, preventing this function to leak any secrets via
188186
* the signatures (see the ecdsa-s2c module in secp256k1-zpk for more details).
189187
*
190-
* @param[in] keypath derivation keypath
191-
* @param[in] keypath_len size of keypath buffer
188+
* @param[in] private_key 32 byte private key
192189
* @param[in] msg32 32 byte message to sign
193190
* @param[in] host_nonce32 32 byte nonce contribution. Cannot be NULL.
194191
* Intended to be a contribution by the host. If there is none available, use 32 zero bytes.
@@ -199,8 +196,7 @@ USE_RESULT bool keystore_secp256k1_nonce_commit(
199196
*/
200197
// clang-format on
201198
USE_RESULT bool keystore_secp256k1_sign(
202-
const uint32_t* keypath,
203-
size_t keypath_len,
199+
const uint8_t* private_key,
204200
const uint8_t* msg32,
205201
const uint8_t* host_nonce32,
206202
uint8_t* sig_compact_out,

src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ pub async fn process(
9898
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
9999
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
100100
let signer_commitment = keystore::secp256k1_nonce_commit(
101-
keypath,
101+
crate::keystore::secp256k1_get_private_key(keypath)?
102+
.as_slice()
103+
.try_into()
104+
.unwrap(),
102105
&sighash,
103106
commitment
104107
.as_slice()
@@ -114,8 +117,14 @@ pub async fn process(
114117
None => [0; 32],
115118
};
116119

117-
let sign_result = bitbox02::keystore::secp256k1_sign(keypath, &sighash, &host_nonce)?;
118-
120+
let sign_result = bitbox02::keystore::secp256k1_sign(
121+
crate::keystore::secp256k1_get_private_key(keypath)?
122+
.as_slice()
123+
.try_into()
124+
.unwrap(),
125+
&sighash,
126+
&host_nonce,
127+
)?;
119128
let mut signature: Vec<u8> = sign_result.signature.to_vec();
120129
signature.push(sign_result.recid);
121130

src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,11 +1203,12 @@ async fn _process(
12031203
sighash_flags: SIGHASH_ALL,
12041204
});
12051205

1206+
let private_key = crate::keystore::secp256k1_get_private_key(&tx_input.keypath)?;
12061207
// Engage in the Anti-Klepto protocol if the host sends a host nonce commitment.
12071208
let host_nonce: [u8; 32] = match tx_input.host_nonce_commitment {
12081209
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
12091210
let signer_commitment = bitbox02::keystore::secp256k1_nonce_commit(
1210-
&tx_input.keypath,
1211+
private_key.as_slice().try_into().unwrap(),
12111212
&sighash,
12121213
commitment
12131214
.as_slice()
@@ -1230,8 +1231,12 @@ async fn _process(
12301231
None => [0; 32],
12311232
};
12321233

1233-
let sign_result =
1234-
bitbox02::keystore::secp256k1_sign(&tx_input.keypath, &sighash, &host_nonce)?;
1234+
let sign_result = bitbox02::keystore::secp256k1_sign(
1235+
private_key.as_slice().try_into().unwrap(),
1236+
&sighash,
1237+
&host_nonce,
1238+
)?;
1239+
drop(private_key);
12351240
next_response.next.has_signature = true;
12361241
next_response.next.signature = sign_result.signature.to_vec();
12371242
}

src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,10 @@ pub async fn _process(
389389
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
390390
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
391391
let signer_commitment = keystore::secp256k1_nonce_commit(
392-
request.keypath(),
392+
&crate::keystore::secp256k1_get_private_key(request.keypath())?
393+
.as_slice()
394+
.try_into()
395+
.unwrap(),
393396
&hash,
394397
commitment
395398
.as_slice()
@@ -404,8 +407,14 @@ pub async fn _process(
404407
// Return signature directly without the anti-klepto protocol, for backwards compatibility.
405408
None => [0; 32],
406409
};
407-
let sign_result = keystore::secp256k1_sign(request.keypath(), &hash, &host_nonce)?;
408-
410+
let sign_result = keystore::secp256k1_sign(
411+
&crate::keystore::secp256k1_get_private_key(request.keypath())?
412+
.as_slice()
413+
.try_into()
414+
.unwrap(),
415+
&hash,
416+
&host_nonce,
417+
)?;
409418
let mut signature: Vec<u8> = sign_result.signature.to_vec();
410419
signature.push(sign_result.recid);
411420

src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,10 @@ pub async fn process(
563563
let host_nonce = match request.host_nonce_commitment {
564564
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
565565
let signer_commitment = keystore::secp256k1_nonce_commit(
566-
&request.keypath,
566+
crate::keystore::secp256k1_get_private_key(&request.keypath)?
567+
.as_slice()
568+
.try_into()
569+
.unwrap(),
567570
&sighash,
568571
commitment
569572
.as_slice()
@@ -578,8 +581,14 @@ pub async fn process(
578581
_ => return Err(Error::InvalidInput),
579582
};
580583

581-
let sign_result = bitbox02::keystore::secp256k1_sign(&request.keypath, &sighash, &host_nonce)?;
582-
584+
let sign_result = bitbox02::keystore::secp256k1_sign(
585+
crate::keystore::secp256k1_get_private_key(&request.keypath)?
586+
.as_slice()
587+
.try_into()
588+
.unwrap(),
589+
&sighash,
590+
&host_nonce,
591+
)?;
583592
let mut signature: Vec<u8> = sign_result.signature.to_vec();
584593
signature.push(sign_result.recid);
585594

src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ pub async fn process(
6767
// Engage in the anti-klepto protocol if the host sends a host nonce commitment.
6868
Some(pb::AntiKleptoHostNonceCommitment { ref commitment }) => {
6969
let signer_commitment = keystore::secp256k1_nonce_commit(
70-
&request.keypath,
70+
crate::keystore::secp256k1_get_private_key(&request.keypath)?
71+
.as_slice()
72+
.try_into()
73+
.unwrap(),
7174
&sighash,
7275
commitment
7376
.as_slice()
@@ -83,8 +86,14 @@ pub async fn process(
8386
None => [0; 32],
8487
};
8588

86-
let sign_result = bitbox02::keystore::secp256k1_sign(&request.keypath, &sighash, &host_nonce)?;
87-
89+
let sign_result = bitbox02::keystore::secp256k1_sign(
90+
crate::keystore::secp256k1_get_private_key(&request.keypath)?
91+
.as_slice()
92+
.try_into()
93+
.unwrap(),
94+
&sighash,
95+
&host_nonce,
96+
)?;
8897
let mut signature: Vec<u8> = sign_result.signature.to_vec();
8998
signature.push(sign_result.recid);
9099

src/rust/bitbox02/src/keystore.rs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,15 @@ pub struct SignResult {
218218
}
219219

220220
pub fn secp256k1_sign(
221-
keypath: &[u32],
221+
private_key: &[u8; 32],
222222
msg: &[u8; 32],
223223
host_nonce: &[u8; 32],
224224
) -> Result<SignResult, ()> {
225225
let mut signature = [0u8; 64];
226226
let mut recid: util::c_types::c_int = 0;
227227
match unsafe {
228228
bitbox02_sys::keystore_secp256k1_sign(
229-
keypath.as_ptr(),
230-
keypath.len() as _,
229+
private_key.as_ptr(),
231230
msg.as_ptr(),
232231
host_nonce.as_ptr(),
233232
signature.as_mut_ptr(),
@@ -243,15 +242,14 @@ pub fn secp256k1_sign(
243242
}
244243

245244
pub fn secp256k1_nonce_commit(
246-
keypath: &[u32],
245+
private_key: &[u8; 32],
247246
msg: &[u8; 32],
248247
host_commitment: &[u8; 32],
249248
) -> Result<[u8; EC_PUBLIC_KEY_LEN], ()> {
250249
let mut signer_commitment = [0u8; EC_PUBLIC_KEY_LEN];
251250
match unsafe {
252251
bitbox02_sys::keystore_secp256k1_nonce_commit(
253-
keypath.as_ptr(),
254-
keypath.len() as _,
252+
private_key.as_ptr(),
255253
msg.as_ptr(),
256254
host_commitment.as_ptr(),
257255
signer_commitment.as_mut_ptr(),
@@ -343,16 +341,14 @@ mod tests {
343341

344342
#[test]
345343
fn test_secp256k1_sign() {
346-
lock();
347-
let keypath = [44 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 5];
344+
let private_key =
345+
hex::decode("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c")
346+
.unwrap();
348347
let msg = [0x88u8; 32];
349348
let host_nonce = [0x56u8; 32];
350349

351-
// Fails because keystore is locked.
352-
assert!(secp256k1_sign(&keypath, &msg, &host_nonce).is_err());
353-
354-
mock_unlocked();
355-
let sign_result = secp256k1_sign(&keypath, &msg, &host_nonce).unwrap();
350+
let sign_result =
351+
secp256k1_sign(&private_key.try_into().unwrap(), &msg, &host_nonce).unwrap();
356352
// Verify signature against expected pubkey.
357353

358354
let secp = secp256k1::Secp256k1::new();
@@ -426,16 +422,15 @@ mod tests {
426422

427423
#[test]
428424
fn test_secp256k1_nonce_commit() {
429-
lock();
430-
let keypath = [44 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 5];
425+
let private_key =
426+
hex::decode("a2d8cf543c60d65162b5a06f0cef9760c883f8aa09f31236859faa85d0b74c7c")
427+
.unwrap();
431428
let msg = [0x88u8; 32];
432429
let host_commitment = [0xabu8; 32];
433430

434-
// Fails because keystore is locked.
435-
assert!(secp256k1_nonce_commit(&keypath, &msg, &host_commitment).is_err());
436-
437-
mock_unlocked();
438-
let client_commitment = secp256k1_nonce_commit(&keypath, &msg, &host_commitment).unwrap();
431+
let client_commitment =
432+
secp256k1_nonce_commit(&private_key.try_into().unwrap(), &msg, &host_commitment)
433+
.unwrap();
439434
assert_eq!(
440435
hex::encode(client_commitment),
441436
"0381e4136251c87f2947b735159c6dd644a7b58d35b437e20c878e5129f1320e5e",

test/unit-test/test_keystore_antiklepto.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ static void _test_keystore_antiklepto(void** state)
9393

9494
// Commit - protocol step 2.
9595
assert_true(keystore_secp256k1_nonce_commit(
96-
keypath, 5, msg, host_nonce_commitment, signer_commitment));
96+
xprv_derived.priv_key + 1, msg, host_nonce_commitment, signer_commitment));
9797
// Protocol step 3: host_nonce sent from host to signer to be used in step 4
9898
// Sign - protocol step 4.
99-
assert_true(keystore_secp256k1_sign(keypath, 5, msg, host_nonce, sig, &recid));
99+
assert_true(
100+
keystore_secp256k1_sign(xprv_derived.priv_key + 1, msg, host_nonce, sig, &recid));
100101

101102
// Protocol step 5: host verification.
102103
secp256k1_ecdsa_signature parsed_signature;

0 commit comments

Comments
 (0)