Skip to content

Commit ec98a85

Browse files
committed
txhash: Move #in/out to in/out byte and add control bit
1 parent 28fcd5c commit ec98a85

File tree

1 file changed

+119
-62
lines changed

1 file changed

+119
-62
lines changed

bip-txhash.mediawiki

+119-62
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,30 @@ OP_TXHASH does the following:
5454
The TxFieldSelector has the following semantics. We will give a brief conceptual
5555
summary, followed by a reference implementation of the CalculateTxHash function.
5656

57-
* If the TxFieldSelector is zero bytes long, it is set equal to
58-
0xff|0xf6|0x3f|0x3f, the de-facto default value which means everything except
59-
the prevouts and the prevout scriptPubkeys.
57+
* There are two special cases for the TxFieldSelector:
58+
- the empty value, zero bytes long: it is set equal to 0xff|0xf6|0xbf|0xbf,
59+
the de-facto default value which means everything except the prevouts and
60+
the prevout scriptPubkeys.
61+
- the 0x00 byte: it is set equal to 0xff|0xff|0xbf|0xbf, which means "ALL"
62+
and is primarily useful to emulate SIGHASH_ALL when OP_TXHASH is used in
63+
combination with OP_CHECKSIGFROMSTACK.
64+
6065
* The first byte of the TxFieldSelector has its 8 bits assigned as follows,
6166
from lowest to highest:
6267
1. version
6368
2. locktime
64-
3. nb_inputs
65-
4. nb_outputs
66-
5. current input index
67-
6. current input control block (only in case of tapscript spend)
68-
7. inputs
69-
8. outputs
69+
3. current input index
70+
4. current input control block (only in case of tapscript spend)
71+
5. current script last OP_CODESEPARATOR position
72+
6. inputs
73+
7. outputs
74+
75+
* The last (highest) bit of the first byte, we will call the "control bit", and
76+
it can be used to control the behavior of the opcode. For OP_TXHASH and
77+
OP_CHECKTXHASHVERIFY, the control bit is used to determine whether the
78+
TxFieldSelector itself has to be included in the resulting hash. (For
79+
potential other uses of the TxFieldSelector (like a hypothetical OP_TX), this
80+
bit can be repurposed.)
7081
7182
* If either "inputs" or "outputs" is set to 1, expect another byte with its 8
7283
bits assigning the following variables, from lowest to highest:
@@ -84,21 +95,24 @@ summary, followed by a reference implementation of the CalculateTxHash function.
8495
For both inputs and then outputs, do the following:
8596

8697
* If the "in/outputs" field is set to 1, another additional byte is expected:
87-
* There are two exceptional values:
88-
- 0x00 means "select only the in/output of the current input index".
89-
- 0x3f means "select all in/outputs". //TODO(stevenroose) make this 0xff?
90-
* The highest bit is the "specification mode":
98+
* The highest bit indicates whether the "number of in-/outputs" should be
99+
committed to.
100+
* For the remaining bits, there are three exceptional values:
101+
- 0x00 means "no in/outputs" (hence only the number of them as 0x80)
102+
- 0x40 means "select only the in/output of the current input index".
103+
- 0x3f means "select all in/outputs".
104+
* The second highest bit is the "specification mode":
91105
- Set to 0 it means "prefix mode".
92106
- Set to 1 it means "individual mode".
93-
* The second highest bit is used to indicate the "index size", i.e. the number
107+
* The third highest bit is used to indicate the "index size", i.e. the number
94108
of bytes will be used to represent in/output indices.
95109
* In "prefix mode",
96-
- With "index size" set to 0, the remaining lowest 6 bits of the first byte
110+
- With "index size" set to 0, the remaining lowest 5 bits of the first byte
97111
will be interpreted as the number of leading in/outputs to select.
98-
- With "index size" set to 1, the remaining lowest 6 bits of the first byte
112+
- With "index size" set to 1, the remaining lowest 5 bits of the first byte
99113
together with the 8 bits of the next byte will be interpreted as the
100114
number of leading in/outputs to select.
101-
* In "individual mode", the remaining lowest 6 bits of the first byte will be
115+
* In "individual mode", the remaining lowest 5 bits of the first byte will be
102116
interpreted as `n`, the number of individual in/outputs to select.
103117
- With "index size" set to 0, interpret the following `n` individual bytes
104118
as the indices of an individual in/outputs to select.
@@ -224,21 +238,22 @@ some potential future upgrades:
224238
<source lang="rust">
225239
pub const TXFS_VERSION: u8 = 1 << 0;
226240
pub const TXFS_LOCKTIME: u8 = 1 << 1;
227-
pub const TXFS_NB_INPUTS: u8 = 1 << 2;
228-
pub const TXFS_NB_OUTPUTS: u8 = 1 << 3;
229-
pub const TXFS_CURRENT_INPUT_IDX: u8 = 1 << 4;
230-
pub const TXFS_CURRENT_INPUT_CONTROL_BLOCK: u8 = 1 << 5;
231-
pub const TXFS_INPUTS: u8 = 1 << 6;
232-
pub const TXFS_OUTPUTS: u8 = 1 << 7;
241+
pub const TXFS_CURRENT_INPUT_IDX: u8 = 1 << 2;
242+
pub const TXFS_CURRENT_INPUT_CONTROL_BLOCK: u8 = 1 << 3;
243+
pub const TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS: u8 = 1 << 4;
244+
pub const TXFS_INPUTS: u8 = 1 << 5;
245+
pub const TXFS_OUTPUTS: u8 = 1 << 6;
246+
247+
pub const TXFS_CONTROL: u8 = 1 << 7;
233248

234249
pub const TXFS_ALL: u8 = TXFS_VERSION
235250
| TXFS_LOCKTIME
236-
| TXFS_NB_INPUTS
237-
| TXFS_NB_OUTPUTS
238251
| TXFS_CURRENT_INPUT_IDX
239252
| TXFS_CURRENT_INPUT_CONTROL_BLOCK
253+
| TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS
240254
| TXFS_INPUTS
241-
| TXFS_OUTPUTS;
255+
| TXFS_OUTPUTS
256+
| TXFS_CONTROL;
242257

243258
pub const TXFS_INPUTS_PREVOUTS: u8 = 1 << 0;
244259
pub const TXFS_INPUTS_SEQUENCES: u8 = 1 << 1;
@@ -261,25 +276,39 @@ pub const TXFS_INPUTS_DEFAULT: u8 = TXFS_INPUTS_SEQUENCES
261276
| TXFS_INPUTS_TAPROOT_ANNEXES;
262277
pub const TXFS_OUTPUTS_ALL: u8 = TXFS_OUTPUTS_SCRIPT_PUBKEYS | TXFS_OUTPUTS_VALUES;
263278

264-
pub const TXFS_INOUT_RANGE_CURRENT: u8 = 0x00;
265-
pub const TXFS_INOUT_RANGE_ALL: u8 = 0xf3;
266-
pub const TXFS_INOUT_RANGE_MODE: u8 = 1 << 7;
267-
pub const TXFS_INOUT_RANGE_SIZE: u8 = 1 << 6;
268-
pub const TXFS_INOUT_RANGE_MASK: u8 = 0xff ^ TXFS_INOUT_RANGE_MODE ^ TXFS_INOUT_RANGE_SIZE;
279+
pub const TXFS_INOUT_NUMBER: u8 = 1 << 7;
280+
pub const TXFS_INOUT_RANGE_NONE: u8 = 0x00;
281+
pub const TXFS_INOUT_RANGE_CURRENT: u8 = 0x40;
282+
pub const TXFS_INOUT_RANGE_ALL: u8 = 0x3f;
283+
pub const TXFS_INOUT_RANGE_MODE: u8 = 1 << 6;
284+
pub const TXFS_INOUT_RANGE_SIZE: u8 = 1 << 5;
285+
pub const TXFS_INOUT_RANGE_MASK: u8 =
286+
0xff ^ TXFS_INOUT_NUMBER ^ TXFS_INOUT_RANGE_MODE ^ TXFS_INOUT_RANGE_SIZE;
269287

270-
pub const DEFAULT_TXFS: [u8; 4] = [
288+
pub const TXFS_TEMPLATE_ALL: [u8; 4] = [
289+
TXFS_ALL,
290+
TXFS_INPUTS_ALL | TXFS_OUTPUTS_ALL,
291+
TXFS_INOUT_NUMBER | TXFS_INOUT_RANGE_ALL,
292+
TXFS_INOUT_NUMBER | TXFS_INOUT_RANGE_ALL,
293+
];
294+
pub const TXFS_TEMPLATE_DEFAULT: [u8; 4] = [
271295
TXFS_ALL,
272296
TXFS_INPUTS_DEFAULT | TXFS_OUTPUTS_ALL,
273-
TXFS_INOUT_RANGE_ALL,
274-
TXFS_INOUT_RANGE_ALL,
297+
TXFS_INOUT_NUMBER | TXFS_INOUT_RANGE_ALL,
298+
TXFS_INOUT_NUMBER | TXFS_INOUT_RANGE_ALL,
275299
];
276300

301+
277302
fn validate_txfieldselector_inout_range(
278303
bytes: &mut impl Iterator<Item = u8>,
279304
nb_items: usize,
280305
) -> Result<(), &'static str> {
281-
let range = bytes.next().ok_or("unexpected EOF")?;
282-
if range == TXFS_INOUT_RANGE_ALL || range == TXFS_INOUT_RANGE_CURRENT {
306+
let nb_mask = 0xff ^ TXFS_INOUT_NUMBER;
307+
let range = bytes.next().ok_or("unexpected EOF")? & nb_mask;
308+
if range == TXFS_INOUT_RANGE_NONE
309+
|| range == TXFS_INOUT_RANGE_ALL
310+
|| range == TXFS_INOUT_RANGE_CURRENT
311+
{
283312
return Ok(())
284313
} else if range & TXFS_INOUT_RANGE_MODE == 0 { // leading mode
285314
if range & TXFS_INOUT_RANGE_SIZE == 0 {
@@ -343,7 +372,7 @@ pub fn validate_txfieldselector(
343372
nb_inputs: usize,
344373
nb_outputs: usize,
345374
) -> Result<(), &'static str> {
346-
if txfs.is_empty() {
375+
if txfs.is_empty() || (txfs.len() == 1 && txfs[0] == 0x00) {
347376
return Ok(());
348377
}
349378

@@ -383,54 +412,70 @@ pub fn validate_txfieldselector(
383412
Ok(())
384413
}
385414

415+
///
416+
///
417+
/// Assumes that TxFieldSelector is valid.
386418
pub fn calculate_txhash(
387419
txfs: &[u8],
388420
tx: &Transaction,
389421
prevouts: &[TxOut],
390422
current_input_idx: u32,
423+
current_input_last_codeseparator_pos: Option<u32>,
391424
) -> sha256::Hash {
392425
assert!(validate_txfieldselector(txfs, tx.input.len(), tx.output.len()).is_ok());
393426
assert_eq!(tx.input.len(), prevouts.len());
394427

395428
let txfs = if txfs.is_empty() {
396-
&DEFAULT_TXFS
429+
&TXFS_TEMPLATE_DEFAULT
430+
} else if txfs.len() == 1 && txfs[0] == 0x00 {
431+
&TXFS_TEMPLATE_ALL
397432
} else {
398433
txfs
399434
};
400435

401436
let mut engine = sha256::Hash::engine();
402437

403-
//TODO(stevenroose) up for debate still
404-
// engine.input(txfs).unwrap();
438+
if txfs[0] & TXFS_CONTROL != 0 {
439+
engine.input(txfs);
440+
}
405441

406442
let mut bytes = txfs.iter().copied();
407443
let global = bytes.next().unwrap();
408444
if global & TXFS_VERSION != 0 {
409445
tx.version.consensus_encode(&mut engine).unwrap();
410446
}
411-
412447
if global & TXFS_LOCKTIME != 0 {
413448
tx.lock_time.consensus_encode(&mut engine).unwrap();
414449
}
415-
416-
if global & TXFS_NB_INPUTS != 0 {
417-
(tx.input.len() as u32).consensus_encode(&mut engine).unwrap();
418-
}
419-
420-
if global & TXFS_NB_OUTPUTS != 0 {
421-
(tx.output.len() as u32).consensus_encode(&mut engine).unwrap();
422-
}
423-
424450
if global & TXFS_CURRENT_INPUT_IDX != 0 {
425451
(current_input_idx as u32).consensus_encode(&mut engine).unwrap();
426452
}
453+
let cur = current_input_idx as usize;
454+
if global & TXFS_CURRENT_INPUT_CONTROL_BLOCK != 0 {
455+
if prevouts[cur].script_pubkey.is_v1_p2tr() {
456+
if let Some(cb) = tx.input[cur].witness.taproot_control_block() {
457+
encode::consensus_encode_with_size(cb, &mut engine).unwrap();
458+
}
459+
//TODO(stevenroose) should we hash something in case of no CB?
460+
}
461+
}
462+
if global & TXFS_CURRENT_INPUT_LAST_CODESEPARATOR_POS != 0 {
463+
if let Some(pos) = current_input_last_codeseparator_pos {
464+
(pos as u32).consensus_encode(&mut engine).unwrap();
465+
}
466+
//TODO(stevenroose) should we hash something in case of no codeseparator?
467+
}
427468

428469
let inout_fields = bytes.next();
429470
if global & TXFS_INPUTS != 0 {
430471
let inout_fields = inout_fields.unwrap();
431-
let range = bytes.next().unwrap();
472+
let input_byte = bytes.next().unwrap();
473+
let number = input_byte & TXFS_INOUT_NUMBER != 0;
474+
let range = input_byte & (0xff ^ TXFS_INOUT_NUMBER);
432475

433-
let inputs: Vec<usize> = if range == TXFS_INOUT_RANGE_ALL {
476+
let inputs: Vec<usize> = if range == TXFS_INOUT_RANGE_NONE {
477+
vec![]
478+
} else if range == TXFS_INOUT_RANGE_ALL {
434479
(0..tx.input.len()).collect()
435480
} else if range == TXFS_INOUT_RANGE_CURRENT {
436481
vec![current_input_idx as usize]
@@ -457,7 +502,11 @@ pub fn calculate_txhash(
457502
selected
458503
};
459504

460-
if inout_fields & TXFS_INPUTS_PREVOUTS != 0 {
505+
if number {
506+
(tx.input.len() as u32).consensus_encode(&mut engine).unwrap();
507+
}
508+
509+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_PREVOUTS != 0 {
461510
let hash = {
462511
let mut engine = sha256::Hash::engine();
463512
for i in &inputs {
@@ -468,7 +517,7 @@ pub fn calculate_txhash(
468517
engine.input(&hash[..]);
469518
}
470519

471-
if inout_fields & TXFS_INPUTS_SEQUENCES != 0 {
520+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_SEQUENCES != 0 {
472521
let hash = {
473522
let mut engine = sha256::Hash::engine();
474523
for i in &inputs {
@@ -479,7 +528,7 @@ pub fn calculate_txhash(
479528
engine.input(&hash[..]);
480529
}
481530

482-
if inout_fields & TXFS_INPUTS_SCRIPTSIGS != 0 {
531+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_SCRIPTSIGS != 0 {
483532
let hash = {
484533
let mut engine = sha256::Hash::engine();
485534
for i in &inputs {
@@ -490,7 +539,7 @@ pub fn calculate_txhash(
490539
engine.input(&hash[..]);
491540
}
492541

493-
if inout_fields & TXFS_INPUTS_PREV_SCRIPTPUBKEYS != 0 {
542+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_PREV_SCRIPTPUBKEYS != 0 {
494543
let hash = {
495544
let mut engine = sha256::Hash::engine();
496545
for i in &inputs {
@@ -501,7 +550,7 @@ pub fn calculate_txhash(
501550
engine.input(&hash[..]);
502551
}
503552

504-
if inout_fields & TXFS_INPUTS_PREV_VALUES != 0 {
553+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_PREV_VALUES != 0 {
505554
let hash = {
506555
let mut engine = sha256::Hash::engine();
507556
for i in &inputs {
@@ -512,7 +561,7 @@ pub fn calculate_txhash(
512561
engine.input(&hash[..]);
513562
}
514563

515-
if inout_fields & TXFS_INPUTS_TAPROOT_ANNEXES != 0 {
564+
if !inputs.is_empty() && inout_fields & TXFS_INPUTS_TAPROOT_ANNEXES != 0 {
516565
let hash = {
517566
let mut engine = sha256::Hash::engine();
518567
for i in &inputs {
@@ -532,9 +581,13 @@ pub fn calculate_txhash(
532581

533582
if global & TXFS_OUTPUTS != 0 {
534583
let output_fields = inout_fields.unwrap();
535-
let range = bytes.next().unwrap();
584+
let output_byte = bytes.next().unwrap();
585+
let number = output_byte & TXFS_INOUT_NUMBER != 0;
586+
let range = output_byte & (0xff ^ TXFS_INOUT_NUMBER);
536587

537-
let outputs: Vec<usize> = if range == TXFS_INOUT_RANGE_ALL {
588+
let outputs: Vec<usize> = if range == TXFS_INOUT_RANGE_NONE {
589+
vec![]
590+
} else if range == TXFS_INOUT_RANGE_ALL {
538591
(0..tx.output.len()).collect()
539592
} else if range == TXFS_INOUT_RANGE_CURRENT {
540593
vec![current_input_idx as usize]
@@ -561,7 +614,11 @@ pub fn calculate_txhash(
561614
selected
562615
};
563616

564-
if output_fields & TXFS_OUTPUTS_SCRIPT_PUBKEYS != 0 {
617+
if number {
618+
(tx.output.len() as u32).consensus_encode(&mut engine).unwrap();
619+
}
620+
621+
if !outputs.is_empty() && output_fields & TXFS_OUTPUTS_SCRIPT_PUBKEYS != 0 {
565622
let hash = {
566623
let mut engine = sha256::Hash::engine();
567624
for i in &outputs {
@@ -572,7 +629,7 @@ pub fn calculate_txhash(
572629
hash.consensus_encode(&mut engine).unwrap();
573630
}
574631

575-
if output_fields & TXFS_OUTPUTS_VALUES != 0 {
632+
if !outputs.is_empty() && output_fields & TXFS_OUTPUTS_VALUES != 0 {
576633
let hash = {
577634
let mut engine = sha256::Hash::engine();
578635
for i in &outputs {

0 commit comments

Comments
 (0)