# CON-005: AGG_SIG Additional Data Derivation

> **Authoritative requirement:** [CON-005](../NORMATIVE.md#CON-005)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 4.3

---

## Summary

The AGG_SIG additional data values provide domain separation for BLS signature verification across different condition opcodes and different networks. The `agg_sig_me_additional_data` field is equal to the genesis challenge itself. All other AGG_SIG additional data values are derived by computing `sha256(genesis_challenge || opcode_byte)` where `opcode_byte` is the single-byte identifier for each AGG_SIG condition opcode. This derivation ensures that signatures are bound to both the specific network and the specific condition type, preventing cross-network and cross-opcode signature replay attacks.

On Chia L1, this derivation is performed in [`condition_tools.py:58-71`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58), where the additional data for each AGG_SIG opcode is precomputed from the genesis challenge. The values are stored in `ConsensusConstants` and used during BLS signature verification to construct the full message that is signed: `message = msg_body || coin_fields || additional_data`. Without domain separation, a signature for AGG_SIG_ME could be reused as a valid AGG_SIG_PARENT signature, or a mainnet signature could be replayed on testnet.

These values must be recomputed whenever the genesis challenge changes. Since DIG L2 currently uses placeholder genesis challenges (CON-003), the derived AGG_SIG values are also placeholders. When the final genesis challenges are determined at mainnet launch, all derived values must be recomputed using the derivation formula specified here.

---

## Specification

### Derivation Formula

```
agg_sig_me_additional_data       = genesis_challenge
agg_sig_parent_additional_data   = sha256(genesis_challenge || 0x30)
agg_sig_puzzle_additional_data   = sha256(genesis_challenge || 0x32)
agg_sig_amount_additional_data   = sha256(genesis_challenge || 0x33)
agg_sig_puzzle_amount_additional_data  = sha256(genesis_challenge || 0x34)
agg_sig_parent_amount_additional_data  = sha256(genesis_challenge || 0x35)
agg_sig_parent_puzzle_additional_data  = sha256(genesis_challenge || 0x36)
```

### Opcode to Byte Mapping

| Opcode Number | Condition Name | Derivation Byte |
|--------------|---------------|-----------------|
| 49 | AGG_SIG_ME | N/A (equals genesis_challenge) |
| 50 | AGG_SIG_PARENT | 0x30 |
| 51 | AGG_SIG_PUZZLE | 0x32 |
| 52 | AGG_SIG_AMOUNT | 0x33 |
| 53 | AGG_SIG_PUZZLE_AMOUNT | 0x34 |
| 54 | AGG_SIG_PARENT_AMOUNT | 0x35 |
| 55 | AGG_SIG_PARENT_PUZZLE | 0x36 |
| 48 | AGG_SIG_UNSAFE | N/A (no additional data) |

Note: `AGG_SIG_UNSAFE` (opcode 48) does not use additional data — the message is the raw `msg_body` without domain separation. This is why it is called "unsafe."

### Implementation

```rust
use sha2::{Sha256, Digest};
use chia_protocol::Bytes32;

/// Compute AGG_SIG additional data values from a genesis challenge.
/// Called during constant construction.
fn derive_agg_sig_data(genesis_challenge: &Bytes32) -> AggSigData {
    AggSigData {
        // AGG_SIG_ME: equals genesis challenge directly
        agg_sig_me: *genesis_challenge,

        // Others: sha256(genesis_challenge || opcode_byte)
        agg_sig_parent: sha256_with_byte(genesis_challenge, 0x30),
        agg_sig_puzzle: sha256_with_byte(genesis_challenge, 0x32),
        agg_sig_amount: sha256_with_byte(genesis_challenge, 0x33),
        agg_sig_puzzle_amount: sha256_with_byte(genesis_challenge, 0x34),
        agg_sig_parent_amount: sha256_with_byte(genesis_challenge, 0x35),
        agg_sig_parent_puzzle: sha256_with_byte(genesis_challenge, 0x36),
    }
}

fn sha256_with_byte(challenge: &Bytes32, byte: u8) -> Bytes32 {
    let mut hasher = Sha256::new();
    hasher.update(challenge.as_ref());
    hasher.update([byte]);
    Bytes32::new(hasher.finalize().into())
}
```

### ConsensusConstants Fields

```rust
// In the ConsensusConstants struct, these derived values are stored:
ConsensusConstants {
    genesis_challenge,
    agg_sig_me_additional_data: genesis_challenge,  // Same as genesis
    agg_sig_parent_additional_data: sha256(genesis || 0x30),
    agg_sig_puzzle_additional_data: sha256(genesis || 0x32),
    agg_sig_amount_additional_data: sha256(genesis || 0x33),
    agg_sig_puzzle_amount_additional_data: sha256(genesis || 0x34),
    agg_sig_parent_amount_additional_data: sha256(genesis || 0x35),
    agg_sig_parent_puzzle_additional_data: sha256(genesis || 0x36),
    // ...
}
```

### How Additional Data Is Used in Signature Verification

```rust
// During BLS verification, the full signed message is constructed:
// For AGG_SIG_ME:
let message = msg_body
    || coin_id
    || constants.agg_sig_me_additional_data;

// For AGG_SIG_PARENT:
let message = msg_body
    || parent_coin_info
    || constants.agg_sig_parent_additional_data;

// For AGG_SIG_PUZZLE:
let message = msg_body
    || puzzle_hash
    || constants.agg_sig_puzzle_additional_data;

// For AGG_SIG_UNSAFE:
let message = msg_body;  // No additional data!
```

### Recomputation at Launch

When the genesis challenge is finalized:

```rust
// Before launch (placeholder):
genesis_challenge = 0x0000...0000
agg_sig_me = 0x0000...0000
agg_sig_parent = sha256(0x0000...0000 || 0x30)

// After launch (real):
genesis_challenge = 0xabcd...1234  // Determined at genesis
agg_sig_me = 0xabcd...1234         // Updated
agg_sig_parent = sha256(0xabcd...1234 || 0x30)  // Recomputed
// All other AGG_SIG data recomputed similarly
```

---

## Acceptance Criteria

- [ ] `agg_sig_me_additional_data` equals `genesis_challenge` in both `DIG_MAINNET` and `DIG_TESTNET`.
- [ ] All other AGG_SIG additional data values are derived as `sha256(genesis_challenge || opcode_byte)`.
- [ ] The opcode bytes match the L1 derivation (0x30 for PARENT, 0x32 for PUZZLE, etc.).
- [ ] DIG_MAINNET and DIG_TESTNET have different AGG_SIG additional data values (due to different genesis challenges).
- [ ] A comment or documentation note indicates that values must be recomputed when genesis_challenge is finalized.

---

## Implementation Notes

- **Primary file:** `dig-constants/src/constants.rs` (derivation during constant construction).
- **const context challenge:** The `sha256` function from `sha2` crate is typically not `const fn`. If `ConsensusConstants` must be `const`, the derived values may need to be precomputed and hardcoded (computed offline, verified by test). Alternatively, use `lazy_static!` or `once_cell` if `const` is not required.
- **L1 byte values:** The opcode bytes (0x30, 0x32, etc.) come from the Chia L1 implementation and must match exactly. These are NOT the opcode numbers (49, 50, etc.) — they are specific domain separation tags.
- **No derivation for AGG_SIG_UNSAFE:** Opcode 48 (AGG_SIG_UNSAFE) intentionally has no additional data. This makes it "unsafe" because the same signed message could be valid on any network.
- **Testing derivation:** Tests should verify the derivation by computing `sha256(genesis_challenge || byte)` independently and comparing against the stored values.
- **Wallet impact:** Wallets that sign transactions must use the correct additional data for the target network. A wallet that signs with DIG_MAINNET additional data cannot produce valid signatures for DIG_TESTNET.

---

## Verification

### Test Plan

| Test | Type | Description | Expected Result |
|------|------|-------------|-----------------|
| agg_sig_me equals genesis | Unit test | DIG_MAINNET.agg_sig_me_additional_data() == DIG_MAINNET.genesis_challenge() | Equal |
| Derivation correctness | Unit test | Independently compute sha256(genesis \|\| 0x30), compare to stored | Match |
| All opcodes derived | Unit test | Verify all 6 derived values (PARENT through PARENT_PUZZLE) | All correct |
| Networks differ | Unit test | DIG_MAINNET.agg_sig_me != DIG_TESTNET.agg_sig_me | Different (different genesis) |
| Opcode bytes match L1 | Unit test | Verify 0x30, 0x32, 0x33, 0x34, 0x35, 0x36 | Match L1 values |
| No data for UNSAFE | Code review | AGG_SIG_UNSAFE handling uses no additional data | Confirmed |

---

## Source Citations

- **SPEC.md Section 4.3**: "agg_sig_me = genesis_challenge. Others = sha256(genesis_challenge || opcode_byte)."
- **SPEC.md Section 4.3**: "Must be recomputed when genesis_challenge is finalized."
- **L1 reference — condition_tools.py:58-71:** [`condition_tools.py`](https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58) — AGG_SIG additional data derivation.
- **Chia consensus specification:** Domain separation tags for BLS signatures.

---

## References

- [CON-002](CON-002.md) — NetworkConstants type (agg_sig_me_additional_data accessor)
- [CON-003](CON-003.md) — DIG_MAINNET and DIG_TESTNET (genesis challenges source)
- [VAL-012](../../validation/specs/VAL-012.md) — BLS signature verification (uses additional data)
- [PAR-011](../../parity/specs/PAR-011.md) — Mempool flag parity (signature verification context)
