@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
317 lines (225 loc) • 9.73 kB
Markdown
# EpochValidator
Low-level validation utilities for OPNet epoch mining solutions and proofs.
## Overview
`EpochValidator` is a static utility class that provides all the cryptographic primitives and validation logic for the OPNet epoch mining system. It computes preimages, hashes, difficulty scores, and validates complete epoch solutions. It is used internally by `ChallengeSolution` but can also be called directly for lightweight validation without constructing full challenge objects.
The epoch system divides blockchain time into epochs of 5 blocks. Miners find solutions by XOR-ing a target checksum with their public key and a random salt, then SHA-1 hashing the result. The number of matching leading bits between the hash and the target determines the solution's difficulty.
**Source:** `src/epoch/validator/EpochValidator.ts`
## Table of Contents
- [Import](#import)
- [Constants](#constants)
- [Static Methods](#static-methods)
- [sha1](#sha1)
- [calculatePreimage](#calculatepreimage)
- [countMatchingBits](#countmatchingbits)
- [calculateSolution](#calculatesolution)
- [verifySolution](#verifysolution)
- [validateEpochWinner](#validateepochwinner)
- [validateChallengeSolution](#validatechallengesolution)
- [checkDifficulty](#checkdifficulty)
- [getMiningTargetBlock](#getminingtargetblock)
- [Validation Algorithm](#validation-algorithm)
- [Examples](#examples)
- [Related Documentation](#related-documentation)
---
## Import
```typescript
import { EpochValidator } from '@btc-vision/transaction';
```
---
## Constants
| Constant | Value | Visibility | Description |
|----------|-------|------------|-------------|
| `BLOCKS_PER_EPOCH` | `5n` | `private` | Number of Bitcoin blocks per OPNet epoch. Used internally for block range validation. |
---
## Static Methods
### sha1
```typescript
static sha1(data: Uint8Array): Uint8Array
```
Computes the SHA-1 hash of the given data. Uses the `crypto.sha1` function from `@btc-vision/bitcoin`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `Uint8Array` | The data to hash. |
**Returns:** `Uint8Array` -- The 20-byte SHA-1 hash.
### calculatePreimage
```typescript
static calculatePreimage(
checksumRoot: Uint8Array,
publicKey: Uint8Array,
salt: Uint8Array,
): Uint8Array
```
Calculates the mining preimage by XOR-ing three 32-byte inputs byte-by-byte.
| Parameter | Type | Description |
|-----------|------|-------------|
| `checksumRoot` | `Uint8Array` | The 32-byte target checksum from the epoch. |
| `publicKey` | `Uint8Array` | The 32-byte miner public key. |
| `salt` | `Uint8Array` | The 32-byte random salt chosen by the miner. |
**Returns:** `Uint8Array` -- The 32-byte preimage (`checksumRoot XOR publicKey XOR salt`).
**Throws:** `Error('All inputs must be 32 bytes')` if any input is not exactly 32 bytes.
### countMatchingBits
```typescript
static countMatchingBits(hash1: Uint8Array, hash2: Uint8Array): number
```
Counts the number of consecutive matching leading bits between two hashes. The count starts from the most significant bit of the first byte and stops at the first bit mismatch.
| Parameter | Type | Description |
|-----------|------|-------------|
| `hash1` | `Uint8Array` | First hash. |
| `hash2` | `Uint8Array` | Second hash (must be the same length as `hash1`). |
**Returns:** `number` -- The number of consecutive matching leading bits.
**Throws:** `Error('Hashes must be of the same length')` if the hashes have different lengths.
**Example:**
```
hash1: 11010110 01010101 ...
hash2: 11010110 01110101 ...
^-- first mismatch at bit 10
Result: 10 matching bits
```
### calculateSolution
```typescript
static calculateSolution(
targetChecksum: Uint8Array,
publicKey: Uint8Array,
salt: Uint8Array,
): Uint8Array
```
Convenience method that computes `SHA1(calculatePreimage(targetChecksum, publicKey, salt))`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `targetChecksum` | `Uint8Array` | The 32-byte target checksum. |
| `publicKey` | `Uint8Array` | The 32-byte miner public key. |
| `salt` | `Uint8Array` | The 32-byte salt. |
**Returns:** `Uint8Array` -- The 20-byte SHA-1 solution hash.
### verifySolution
```typescript
static verifySolution(challenge: IChallengeSolution, log?: boolean): boolean
```
Verifies a complete challenge solution by:
1. Recalculating the preimage from `targetChecksum`, `publicKey`, and `salt`.
2. Computing SHA-1 of the preimage.
3. Checking that the computed hash matches the stored `solution`.
4. Verifying the difficulty (matching bits) matches the claimed difficulty.
5. Validating the block range (`startBlock` and `endBlock`) against the epoch number.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `challenge` | `IChallengeSolution` | -- | The challenge solution to verify. |
| `log` | `boolean` | `false` | Whether to log errors to console. |
**Returns:** `boolean` -- `true` if all checks pass.
### validateEpochWinner
```typescript
static validateEpochWinner(epochData: RawChallenge): boolean
```
Validates an epoch winner from raw string data without requiring a `ChallengeSolution` instance. Performs the same verification as `verifySolution` but parses the raw data inline.
| Parameter | Type | Description |
|-----------|------|-------------|
| `epochData` | `RawChallenge` | The raw challenge data with string values. |
**Returns:** `boolean` -- `true` if the solution is valid.
### validateChallengeSolution
```typescript
static validateChallengeSolution(challenge: IChallengeSolution): boolean
```
Validates a challenge from an `IChallengeSolution` instance. Equivalent to calling `verifySolution(challenge)`.
| Parameter | Type | Description |
|-----------|------|-------------|
| `challenge` | `IChallengeSolution` | The challenge solution instance to validate. |
**Returns:** `boolean`
### checkDifficulty
```typescript
static checkDifficulty(
solution: Uint8Array,
targetHash: Uint8Array,
minDifficulty: number,
): { valid: boolean; difficulty: number }
```
Checks whether a solution meets a minimum difficulty requirement.
| Parameter | Type | Description |
|-----------|------|-------------|
| `solution` | `Uint8Array` | The solution hash. |
| `targetHash` | `Uint8Array` | The target hash to compare against. |
| `minDifficulty` | `number` | The minimum number of matching leading bits required. |
**Returns:** `{ valid: boolean; difficulty: number }` -- Whether the difficulty requirement is met and the actual difficulty level.
### getMiningTargetBlock
```typescript
static getMiningTargetBlock(epochNumber: bigint): bigint | null
```
Returns the block number that miners should target for the given epoch.
| Parameter | Type | Description |
|-----------|------|-------------|
| `epochNumber` | `bigint` | The epoch number. |
**Returns:** `bigint | null` -- The last block of the previous epoch (`epochNumber * 5 - 1`), or `null` for epoch 0.
---
## Validation Algorithm
The complete validation flow for an epoch solution:
```
Input: epochNumber, publicKey (32 bytes), salt (32 bytes),
solution, difficulty, verification.targetChecksum,
verification.targetHash, verification.startBlock,
verification.endBlock
Step 1: Calculate preimage
preimage[i] = targetChecksum[i] XOR publicKey[i] XOR salt[i]
for i in 0..31
Step 2: Hash the preimage
computedSolution = SHA1(preimage) // 20 bytes
Step 3: Verify solution matches
if computedSolution != solution: FAIL
Step 4: Verify difficulty
matchingBits = countMatchingLeadingBits(computedSolution, targetHash)
if matchingBits != difficulty: FAIL
Step 5: Verify block range
expectedStart = epochNumber * 5
expectedEnd = expectedStart + 4
if startBlock != expectedStart OR endBlock != expectedEnd: FAIL
Result: PASS
```
---
## Examples
### Validating Raw Epoch Data
```typescript
import { EpochValidator } from '@btc-vision/transaction';
const rawData = {
epochNumber: '100',
mldsaPublicKey: '0x...',
legacyPublicKey: '0x02...',
solution: '0x...',
salt: '0x...',
graffiti: '0x...',
difficulty: 22,
verification: {
epochHash: '0x...',
epochRoot: '0x...',
targetHash: '0x...',
targetChecksum: '0x...',
startBlock: '500',
endBlock: '504',
proofs: ['0x...'],
},
};
const isValid = EpochValidator.validateEpochWinner(rawData);
console.log(`Epoch 100 winner valid: ${isValid}`);
```
### Calculating a Solution
```typescript
const targetChecksum = new Uint8Array(32); // from epoch data
const publicKey = new Uint8Array(32); // miner's public key
const salt = new Uint8Array(32); // random salt
const solution = EpochValidator.calculateSolution(targetChecksum, publicKey, salt);
console.log(`Solution (${solution.length} bytes)`);
```
### Checking Difficulty
```typescript
const solution = new Uint8Array(20); // SHA-1 hash
const targetHash = new Uint8Array(20); // epoch target
const result = EpochValidator.checkDifficulty(solution, targetHash, 16);
console.log(`Difficulty: ${result.difficulty}, meets minimum: ${result.valid}`);
```
### Getting the Mining Target Block
```typescript
const targetBlock = EpochValidator.getMiningTargetBlock(42n);
// targetBlock = 42n * 5n - 1n = 209n
const epoch0 = EpochValidator.getMiningTargetBlock(0n);
// epoch0 = null (epoch 0 cannot be mined)
```
---
## Related Documentation
- [ChallengeSolution](./challenge-solution.md) -- High-level challenge solution class
- [Generators](../generators/generators.md) -- How challenge data is embedded in transaction scripts