|
42 | 42 | * that is sound to enforce header-only. |
43 | 43 | */ |
44 | 44 |
|
45 | | -import type { BlockHeader, NetworkType } from "@fairco.in/core"; |
46 | | -import { bytesEqual, hashBlockHeader } from "@fairco.in/core"; |
| 45 | +import type { BlockHeader } from "@fairco.in/core"; |
| 46 | +import { |
| 47 | + bytesEqual, |
| 48 | + hashBlockHeader, |
| 49 | + isValidTargetBits, |
| 50 | + meetsProofOfWork, |
| 51 | +} from "@fairco.in/core"; |
47 | 52 | import type { BlockHeaderMsg } from "./messages"; |
48 | 53 |
|
49 | 54 | // --------------------------------------------------------------------------- |
50 | 55 | // Compact ("nBits") target encoding — Bitcoin/FairCoin `uint256::SetCompact`. |
51 | | -// --------------------------------------------------------------------------- |
52 | | - |
53 | | -export interface CompactTarget { |
54 | | - /** The decoded 256-bit target value. */ |
55 | | - readonly target: bigint; |
56 | | - /** True if the compact encoding had its sign bit set (an invalid target). */ |
57 | | - readonly negative: boolean; |
58 | | - /** True if the mantissa/exponent combination overflows 256 bits. */ |
59 | | - readonly overflow: boolean; |
60 | | -} |
61 | | - |
62 | | -const U256_MASK = (1n << 256n) - 1n; |
63 | | - |
64 | | -/** |
65 | | - * Decode a compact difficulty target ("nBits") into a 256-bit value, faithfully |
66 | | - * reproducing FairCoin's `uint256::SetCompact` including its sign/overflow flags. |
67 | | - */ |
68 | | -export function compactToTarget(bits: number): CompactTarget { |
69 | | - const nSize = (bits >>> 24) & 0xff; |
70 | | - const nWord = bits & 0x007fffff; |
71 | | - |
72 | | - let target: bigint; |
73 | | - if (nSize <= 3) { |
74 | | - target = BigInt(nWord >>> (8 * (3 - nSize))); |
75 | | - } else { |
76 | | - target = (BigInt(nWord) << BigInt(8 * (nSize - 3))) & U256_MASK; |
77 | | - } |
78 | | - |
79 | | - const negative = nWord !== 0 && (bits & 0x00800000) !== 0; |
80 | | - const overflow = |
81 | | - nWord !== 0 && |
82 | | - (nSize > 34 || |
83 | | - (nWord > 0xff && nSize > 33) || |
84 | | - (nWord > 0xffff && nSize > 32)); |
85 | | - |
86 | | - return { target, negative, overflow }; |
87 | | -} |
88 | | - |
89 | | -/** |
90 | | - * The proof-of-work limit (easiest allowed target) for a network, as a 256-bit |
91 | | - * value. Both FairCoin mainnet and testnet use `~uint256(0) >> 20` |
92 | | - * (`CTestNetParams` inherits it from `CMainParams`). Only regtest differs, and |
93 | | - * this wallet never targets regtest. |
94 | | - */ |
95 | | -export function proofOfWorkLimit(): bigint { |
96 | | - return U256_MASK >> 20n; |
97 | | -} |
98 | | - |
99 | | -/** |
100 | | - * Whether a header's `nBits` encodes a valid, in-range difficulty target. |
101 | | - * |
102 | | - * The range half of FairCoin's `CheckProofOfWork`: reject negative, zero, |
103 | | - * overflowing, or above-limit targets. |
104 | | - */ |
105 | | -export function isValidTargetBits(bits: number, powLimit: bigint): boolean { |
106 | | - const { target, negative, overflow } = compactToTarget(bits); |
107 | | - if (negative || overflow) return false; |
108 | | - if (target === 0n) return false; |
109 | | - if (target > powLimit) return false; |
110 | | - return true; |
111 | | -} |
112 | | - |
113 | | -/** |
114 | | - * Read a block hash as the 256-bit number FairCoin compares against the target. |
115 | | - * |
116 | | - * `hashBlockHeader` returns bytes in internal (`uint256` serialisation) order, |
117 | | - * which is little-endian: byte 0 is the least significant. |
118 | | - */ |
119 | | -export function hashToUint256(hash: Uint8Array): bigint { |
120 | | - let value = 0n; |
121 | | - for (let i = hash.length - 1; i >= 0; i--) { |
122 | | - value = (value << 8n) | BigInt(hash[i]); |
123 | | - } |
124 | | - return value; |
125 | | -} |
126 | | - |
127 | | -/** |
128 | | - * The work half of FairCoin's `CheckProofOfWork`: `hash > bnTarget` is a |
129 | | - * failure, so equality passes. |
130 | | - * |
131 | | - * Only meaningful for PoW-era headers — see rule 3 in the module docblock. |
132 | | - */ |
133 | | -export function meetsProofOfWork(hash: Uint8Array, bits: number): boolean { |
134 | | - const { target, negative, overflow } = compactToTarget(bits); |
135 | | - if (negative || overflow || target === 0n) return false; |
136 | | - return hashToUint256(hash) <= target; |
137 | | -} |
138 | | - |
139 | | -/** |
140 | | - * `Params().LAST_POW_BLOCK()` from `chainparams.cpp`. Above this height the |
141 | | - * chain is proof-of-stake and header-only proof-of-work verification is not |
142 | | - * applicable; at or below it, `main.cpp` rejects PoS blocks outright, so every |
143 | | - * header is provably PoW. |
144 | | - * |
145 | | - * Kept here beside {@link proofOfWorkLimit} — the other consensus constant the |
146 | | - * SPV validator needs that is not carried in `NetworkConfig`. |
147 | | - */ |
148 | | -export function lastPowBlock(network: NetworkType): number { |
149 | | - return network === "mainnet" ? 10_000 : 200; |
150 | | -} |
151 | | - |
152 | 56 | // --------------------------------------------------------------------------- |
153 | 57 | // Header chain validation |
154 | 58 | // --------------------------------------------------------------------------- |
|
0 commit comments