Port @sighash / @embedAlways / EmptySig to the remaining 6 tiers (completes #109, #123, #106)#138
Open
icellan wants to merge 24 commits into
Open
Conversation
Translate the TypeScript reference (cf75afc / d46e98e) to the Python tier: - PropertyNode.embed_always parsed from a /** @embedAlways */ (or // @embedAlways) comment directive immediately preceding a readonly field. The TS tokenizer discards comments, so the Python tokenizer now captures leading-comment trivia onto the next token; parser_ts scans it word-boundary matched (mirrors the TS /@embedAlways\b/). - ANF lowering injects a load_prop + @ref alias for each @embedAlways readonly field into the first public method, exactly as `const _bind = this.field;` lowers. The alias keeps the load_prop alive through DCE so stack lowering emits the constructor-slot placeholder and the field's bytes survive in the deployed locking script. - compile-with-result emits a non-fatal warning when DCE strips an un-annotated, unreferenced, initializer-less readonly field, pointing at the directive. - Drop the @embedAlways branch of the PR#1 fail-closed guard (Python now supports it); the @sigHash branch stays until #123 lands. Purely additive/opt-in: no fixture uses the directive, so ANF IR and hex output are byte-identical (zero conformance golden churn). Also forward-adds MethodNode.sighash_type (unused until #123).
…ng layer Translate the completed TypeScript reference (#109 commits cf75afc / d46e98e / b1e8102) to the Go compiler. @embedAlways (#109): - PropertyNode.EmbedAlways, set by the .runar.ts surface parser when a `/** @embedAlways */` or `// @embedAlways` comment directive precedes a readonly field (tree-sitter exposes comments as class-body siblings). - ANF lowering injects a load_prop + @ref alias for each @embedAlways readonly field into the first public method, keeping the field's load_prop alive through dead-binding DCE so its bytes survive into the locking script (a constructor slot). Byte-identical to the TS reference: the annotated Meta contract emits 0078a900887b7bac77 with constructor slots [metadataId, pubKeyHash], the plain one 76a90088ac (field stripped). - Post-lowering DCE warning (Option 4): un-annotated readonly field that no method references is eliminated -> warn, pointing at @embedAlways. Faithful to the TS collectReferencedProps(optimizedAnf) placement (probe DCE, skip constructor). Shared directive-parsing layer (used by @embedAlways and the incoming @sigHash port): - frontend/sighash_directive.go: @sigHash flag grammar (name->value, combo validity, FORKID-mandatory) — a faithful port of sighash-directive.ts. - Parser detects `@sighash <FLAGS>` on public methods -> MethodNode.SighashType (rejects private-method / bad-flag directives). - ParseSource guard narrowed: the .runar.ts surface now honours both directives (matching TS); the eight non-TS surface parsers still fail closed rather than silently drop a directive they cannot read. Guard test repurposed accordingly. Zero conformance impact: no fixture uses the directives; default (no directive) paths are byte-identical.
… to Go Translate the completed TypeScript reference (#123 commits fd73a79 / 411525a / 83b762d / e88f202) to the Go compiler. SECURITY-CRITICAL: replicated exactly, including the e88f202 security-audit fixes. Field-usage validation (frontend/sighash_validate.go, wired into Validate): rejects, per method's declared @sigHash mode, every preimage-field read / output binding the relaxed flag makes unsound — the 5 rules: 1. ANYONECANPAY -> reject extractHashPrevouts + extractPrevOutputScript 2. non-pure-ALL -> reject extractHashSequence 3. NONE -> reject continuation + addOutput/addRawOutput/addDataOutput + requireOutputP2PKH + extractOutputHash/extractOutputs 4. SINGLE -> reject the mutate-only auto-continuation (F1 value-skim) AND >1 committed output AND requireOutputP2PKH (F4); allow an explicit single addOutput with a value-pinning WARNING 5. default (no directive) = ALL|FORKID, never flagged The field-read walk is transitive through private helpers and covers for-loop init/condition + assignment targets (F3, mirrored in side_effect_summary.go). FORKID is mandatory (F2, parse-level). Mode-aware codegen (byte-identical to TS, fold-OFF and fold-ON): - anf_lower.go swaps the hardcoded 0x41 preimage-type assert for the declared mode and threads the flag onto each check_preimage (auto-injected or manual) via ANFValue.SighashFlag; default omits it (zero golden churn). - codegen/oppushtx.go makes the OP_PUSH_TX binding blob flag-parametric (the single 0141 push -> 01<flag>); default returns the frozen constant. - ABIMethod.SigHashType carries a non-default mode (via ANFMethod.SigHashType, an in-memory carrier) so the SDK builds a matching preimage. Parity proven against the TS reference: SINGLE|FORKID -> sighash byte 0x43, ALL|ANYONECANPAY|FORKID -> 0xC1, ALL|FORKID == no directive (byte-identical); each of the 5 unsound cases rejected with a diagnostic.
…itical) Translate the TypeScript reference (fd73a79 / 411525a / 83b762d / af0c84d / e88f202) to the Python compiler + SDK tiers. Compiler: - sighash_directive.py: flag grammar (name->value, exactly-one base type checked on NAMES to catch the 0x03 ALL|NONE aliasing, duplicate reject, and FORKID mandatory / deploy-to-brick reject). Default (no directive) = ALL|FORKID. - parser_ts detects /** @sigHash <FLAGS> */ (and // form) on public methods -> MethodNode.sighash_type; rejects the directive on a non-public method. - anf_lower threads the mode: the auto-injected preimage-type assert pins the declared const, check_preimage carries a non-default sighash_flag, and manual checkPreimage() calls (incl. nested blocks) pick up the method mode. - stack codegen swaps ONLY the appended sighash flag byte in the check-preimage binding blob (01<flag>7e), byte-exact equivalent to regenerating it; the default (0x41) blob is unchanged -> zero golden churn. - sighash_validate.py (wired into validate()) is the security core: rejects every preimage-field read / output binding a relaxed flag makes unsound — ANYONECANPAY (hashPrevouts + prevout-script), non-pure-ALL hashSequence, NONE output bindings, SINGLE multi-output + mutate-only value-skim (F1) + requireOutputP2PKH (F4). The transitive walk covers for-loop headers and assignment targets (F3), matching the side-effect-summary walk fix. - ABI carries a non-default sigHashType; ANF round-trips sighashFlag / sigHashType; constant-fold preserves the method sighash type. - Removes the remaining @sigHash fail-closed guard + its test. SDK: - AbiMethod.sig_hash_type parsed from the artifact JSON. - compute_op_push_tx takes a sig_hash_type: it drives the BIP-143 field zeroing (hashPrevouts under ANYONECANPAY, hashSequence unless pure ALL, hashOutputs under NONE, SINGLE -> same-index output only) AND the appended sighash byte. - RunarContract resolves the method's mode from the ABI and threads it through every preimage build (call + terminal paths). Default (ALL|FORKID) path is byte-identical everywhere; all conformance goldens pass unchanged.
#123 SDK (translate af0c84d): - ComputeOpPushTxWithSigHash builds the BIP-143 preimage + derives the OP_PUSH_TX signature under a caller-supplied sighash type; the go-sdk's CalcInputPreimage does the field zeroing natively (hashPrevouts under ANYONECANPAY, hashSequence unless pure ALL, hashOutputs under NONE / same-index SINGLE), so passing the flag is the complete port — no hand-rolled mock-preimage zeroing needed as in the TS testing tier. ComputeOpPushTxWithCodeSep now delegates with the 0x41 default. - ABIMethod.SigHashType (sdk_types.go) deserializes the artifact field; RunarContract.methodSigHashType resolves a method's declared mode by ABI lookup (default 0x41). Every OP_PUSH_TX call site on the stateful and terminal paths threads it, so a @sigHash SINGLE|FORKID method's preimage carries sighash byte 0x43 and an ANYONECANPAY method 0xC1, with the default unchanged. #106 EmptySig (translate 838c315): - EmptySig sentinel + IsEmptySig guard; encodeArg emits OP_0 for it; the auto-sign collectors skip it (not nil -> never signed). OR-CHECKSIG [realSig, EmptySig] encodes <push sig> || 00 — wire-byte parity with the TS SDK. Purely additive: existing nil/explicit-bytes callers and default-sighash methods are byte-identical.
Port the TypeScript reference (838c315) to the Python SDK. An OR-CHECKSIG method — checkSig(sigA, pkA) || checkSig(sigB, pkB), where || lowers to the non-lazy OP_BOOLOR — runs BOTH CHECKSIGs; the failing branch must push an empty signature (OP_0) or BIP146 NULLFAIL rejects the spend. - EMPTY_SIG singleton sentinel + is_empty_sig() guard (isinstance-based so identity survives duplicate imports). Exported from runar.sdk. - _encode_arg emits OP_0 ("00") for EMPTY_SIG. It is not None, so the sig collectors never add it to sig_indices and never sign it — it reaches _encode_arg as OP_0. call('execute', [None, EMPTY_SIG]) signs only slot 0. - Soft warning when >=2 auto-signed Sig slots remain (likely an OR-CHECKSIG whose non-matching branch should use EMPTY_SIG); informational only. Purely additive: existing None / explicit-bytes callers unchanged. Wire-level parity with TS — the failing branch push is OP_0 under [None, EMPTY_SIG] and non-empty (duplicated sig) under [None, None].
…wrapper The flag-parametric checkPreimageBindingBytesWithFlag is the single source; the 0x41 default flows through emitCheckPreimageBinding. No callers referenced the no-arg wrapper after the #123 codegen port.
…iler) Ports the completed TypeScript reference for two author-facing comment directives to the Rust tier. They share the two-surface comment-directive plumbing (JSDoc block + leading trivia), so the compiler-side work lands together; the PR#1 fail-closed guard is removed since Rust now supports both. #109 @embedAlways (readonly-field DCE opt-out): - parse `/** @embedAlways */` (and `// @embedAlways`) into PropertyNode.embed_always (SWC SingleThreadedComments, by member span) - anf_lower injects a load_prop + `@ref:` alias into the first public method so the field survives dead-binding DCE into a constructor slot - lib.rs emits a compile WARNING when DCE strips an un-annotated, unreferenced readonly field, pointing at the directive #123 @sigHash (per-method BIP-143 sighash type, SECURITY-CRITICAL): - new sighash_directive module: flag grammar (name->value), reject unknown / duplicate / zero-or-multiple base types (ALL|NONE checked on NAMES to catch the 0x03 alias), FORKID mandatory; default absent = ALL|FORKID (0x41) - parser sets MethodNode.sighash_type; errors on a directive on a non-public method or a malformed flag list - new sighash_validate pass (wired into validate) rejects unsound field reads / output bindings per the 5 rules incl. the security-audit hardening: ANYONECANPAY -> extractHashPrevouts / extractPrevOutputScript; non-pure-ALL -> extractHashSequence; NONE -> any output binding; SINGLE -> mutate-only auto-continuation (value skim), >1 committed output, requireOutputP2PKH; explicit single addOutput allowed with a WARNING. Transitive walk covers private helpers, for-loop init/condition, and the assignment target; side_effect_summary walks the same - mode-aware codegen: swap the 0x41 preimage-type assert const for the declared mode; CheckPreimage.sighashFlag threads through stack lowering; the OP_PUSH_TX binding blob's appended flag byte is rewritten in place (single byte, so the default stays byte-identical); ABIMethod.sigHashType surfaces a non-default mode Default (ALL|FORKID) path is byte-identical: all 64 conformance golden scripts unchanged, and @sigHash SINGLE|FORKID / ANYONECANPAY explicit-output contracts compile to byte-identical hex vs the TypeScript reference (0x43 / 0xC1 flags).
…mage compute_op_push_tx_with_code_sep_sighash builds the BIP-143 preimage under a given sighash flag: it zeroes the fields the mode clears (hashPrevouts under ANYONECANPAY, hashSequence unless pure ALL, hashOutputs under NONE / restricted to the same-index output under SINGLE) and appends the declared flag byte to the DER signature. RunarContract resolves the mode from ABIMethod.sigHashType by public-method index (get_sig_hash_type) and threads it through every call/finalize path (stateful, stateless, terminal). AbiMethod gains sig_hash_type. Default 0x41 is unchanged: the explicit-0x41 path is byte-for-byte identical to the pre-existing default path.
Port the TypeScript reference (#109) to the Ruby tier. The .runar.ts parser now detects a /** @embedAlways */ (or // @embedAlways) comment directive immediately preceding a readonly field and sets PropertyNode.embed_always. ANF lowering injects a load_prop + @ref: alias for each @embedAlways readonly field into the first public method (the exact shape 'const _bind = this.field;' produces), keeping the load_prop alive through dead-binding DCE so the field's constructor slot survives into the deployed locking script for downstream recovery. Ruby's tokenizer strips comments, so directive comments are captured during tokenization (with their start line) and attached to the member they immediately precede — replacing ts-morph's leading-comment trivia. Narrows the PR#1 fail-closed guard: @embedAlways now compiles on .runar.ts; the other 8 formats still fail closed. @sigHash remains fully fail-closed until the #123 port lands. Purely additive/opt-in: no existing fixture uses the directive, so ANF IR and hex output are byte-identical (zero golden churn).
…ing layer Translate the completed TypeScript reference (#109 commits cf75afc / d46e98e / b1e8102) to the Zig compiler. @embedAlways (#109): - PropertyNode.embed_always, set by the .runar.ts surface parser when a `/** @embedAlways */` or `// @embedAlways` comment directive precedes a readonly field. The hand-written tokenizer discards comments, so the parser recovers leading comment trivia via byte-offset spans on tokens (source between the previous token's end and the member's first token). - ANF lowering injects a preserved load_prop for each @embedAlways readonly field into the first public method, keeping the field's load_prop alive through DCE so its bytes survive into the locking script (a constructor slot). Byte-identical to the TS reference: the annotated Meta contract emits 0078a900887b7bac77 with constructor slots [metadataId, pubKeyHash], the plain one 76a90088ac (field stripped). - Zig divergence from TS: the TS reference keeps the load_prop alive with a `@ref` alias relying on a single-pass DCE; the Zig ec_optimizer runs a fixpoint DCE that would strip such an unreferenced alias chain, so the Zig tier marks the injected load_prop directly (LoadProp.preserve, honoured by dce.hasSideEffect). Observable output is byte-identical. - Post-optimizer DCE warning (Option 4): un-annotated readonly field that no method references is eliminated -> warn, pointing at @embedAlways. Shared directive-parsing layer (used by @embedAlways and the incoming @sigHash port): - frontend/sighash_directive.zig: @sigHash flag grammar (name->value, combo validity, FORKID-mandatory) — a faithful port of sighash-directive.ts. - Parser detects `@sighash <FLAGS>` on public methods -> MethodNode.sighash_type (rejects private-method / bad-flag / FORKID-less directives). - Guard narrowed (compiler_api.parseSource + main.zig): the .runar.ts surface now honours both directives (matching TS); the eight non-TS surface parsers still fail closed rather than silently drop a directive they cannot read. Zero conformance impact: no fixture uses the directives (64/64 goldens pass); default (no directive) paths are byte-identical.
…ity validation Port the TypeScript reference (#123) to the Ruby compiler tier. A public method may declare a /** @sigHash <FLAGS> */ directive on the .runar.ts surface; absent = default ALL|FORKID (0x41), byte-identical to the pinned mode. Grammar (sighash_directive.rb): name->value mapping + combo validation that rejects unknown flags, zero/multiple base types (ALL|NONE checked on NAMES to catch the 0x03 aliasing collision), duplicates, and a FORKID-less set (deploy-to-brick on BSV). Codegen: the auto-injected preimage-type assert pins the declared mode; the OP_PUSH_TX binding blob appends the mode's flag byte (flag-parametric: only the single appended sighash byte differs from the pinned cross-tier constant); manual checkPreimage calls pick up the mode; the ABI carries a non-default sigHashType; the ANF check_preimage node carries sighash_flag. The mode is threaded on ANFMethod.sighash_type and preserved across the fold + EC deep-copy passes (the check_preimage sighash_flag survives fold via object passthrough and is preserved in the EC deep-copy). Field-usage validation (sighash_validate.rb, security core) replicates all 5 rules with matching diagnostics: ANYONECANPAY rejects extractHashPrevouts / extractPrevOutputScript; non-pure-ALL rejects extractHashSequence; NONE rejects all output bindings; SINGLE rejects the mutate-only continuation (value-skim), >1 committed output, and requireOutputP2PKH, and warns on an explicit single addOutput. The walk is transitive through private helpers and covers for-loop init/condition + assignment targets. Narrows the fail-closed guard: @sigHash compiles on .runar.ts; the other 8 formats still fail closed. Default 0x41 path untouched — zero golden churn.
Port the TS SDK convention (#106) to the Ruby SDK. An OR-CHECKSIG method checkSig(sigA, pkA) || checkSig(sigB, pkB) runs BOTH OP_CHECKSIG branches (|| lowers to the non-lazy OP_BOOLOR); the failing branch MUST push an empty signature (OP_0) or BIP146 NULLFAIL rejects the spend. Adds a globally-interned EMPTY_SIG sentinel + empty_sig? guard on Runar::SDK. encode_arg emits OP_0 for it. The auto-sign collectors only touch nil Sig slots, so EMPTY_SIG is never signed — it reaches encode_arg as OP_0. Coexists with nil at the same call: call('execute', [nil, EMPTY_SIG]) signs only slot 0. Purely additive: existing nil/explicit-bytes callers unchanged. Wire parity with TS is proved at the byte level (Ruby has no off-chain ScriptVM): the failing branch is OP_0 under [nil, EMPTY_SIG] and a non-empty duplicate sig under [nil, nil].
…signing Port the TS SDK threading (#123) to the Ruby SDK. compute_op_push_tx takes an optional sighash_type (default 0x41) driving BOTH the BIP-143 scope (which preimage fields get zeroed) and the appended DER sighash flag byte. RunarContract.resolve_sighash_type looks up the public method's ABI sigHashType by name, so every call / terminal / stateless preimage-build path picks it up; a method with no @sigHash directive falls back to 0x41. The hand-rolled bip143_preimage now mirrors the BIP-143 zeroing: - ANYONECANPAY -> hashPrevouts = 32 zero bytes - non-pure-ALL -> hashSequence = 32 zero bytes - NONE -> hashOutputs = 32 zero bytes - SINGLE -> hashOutputs = hash256(output[inputIndex]) ONLY, or 32 zero bytes out of range (F5) — NOT the whole output set ABIMethod gained sig_hash_type (loaded from the artifact's sigHashType). Default 0x41 path is byte-identical (the existing preimage golden ending in 41000000 is unchanged). The #106 spec's colliding top-level constants were renamed to keep the suite order-independent.
… the .runar.ts surface Port two author-facing comment directives to the Java compiler tier, translating the TypeScript reference. Both are honoured ONLY on the .runar.ts surface; the PR#1 fail-closed guard is NARROWED to keep rejecting them for the other 8 surface formats. #109 @embedAlways (readonly-field DCE opt-out): - PropertyNode.embedAlways; TsParser captures leading-comment trivia (the tokenizer previously discarded comments) and detects the /** @embedAlways */ (or // @embedAlways) directive on a readonly field. - AnfLower injects a load_prop + @ref alias into the first public method (the exact shape `const _bind = this.field;` produces) so the field survives dead-binding DCE into a constructor slot. - Validate emits a warning when DCE strips an un-annotated, unreferenced readonly field, pointing at the directive. #123 @sigHash (per-method BIP-143 sighash mode, security-critical): - MethodNode.sighashType; new SighashDirective grammar (name->value, combo validation, ALL|NONE alias reject on NAMES, FORKID mandatory). - Mode-aware codegen: the auto-injected preimage-type assert pins the declared mode, and CheckPreimage.sighashFlag makes the OP_PUSH_TX binding blob's appended flag byte parametric (StackLower swaps the unique 01 41 7e subsequence). Default 0x41 stays byte-identical. - New SighashValidate pass (5 unsound-usage rules + F1 mutate-only SINGLE reject, F3 for-loop-header/assignment-target walk, F4 requireOutputP2PKH under SINGLE), transitive through private helpers. Verified byte-for-byte Java==TS on default (0x41), SINGLE (0x43), ANYONECANPAY (0xc1) and @embedAlways fixtures via cross-compile, and zero conformance golden churn (60/60 .runar.ts fixtures match).
… to Zig Translate the completed TypeScript reference (#123 commits fd73a79 / 411525a / 83b762d / e88f202) to the Zig compiler. SECURITY-CRITICAL: replicated exactly, including the e88f202 security-audit fixes. Field-usage validation (passes/sighash_validate.zig, wired into validate): rejects, per method's declared @sigHash mode, every preimage-field read / output binding the relaxed flag makes unsound — the 5 rules: 1. ANYONECANPAY -> reject extractHashPrevouts + extractPrevOutputScript 2. non-pure-ALL -> reject extractHashSequence 3. NONE -> reject continuation + addOutput/addRawOutput/addDataOutput + requireOutputP2PKH + extractOutputHash/extractOutputs 4. SINGLE -> reject the mutate-only auto-continuation (F1 value-skim) AND >1 committed output AND requireOutputP2PKH (F4); allow an explicit single addOutput with a value-pinning WARNING 5. default (no directive) = ALL|FORKID, never flagged The field-read walk is transitive through private helpers and covers assignment index expressions. FORKID is mandatory (F2, parse-level). F3 for-loop-header reads are covered by the Zig parser's runtime-bound rejection (a forbidden read in a loop condition is rejected before this pass runs). Mode-aware codegen (byte-identical to TS, verified fold-OFF against the Go tier's TS-reference goldens: default / SINGLE 0x43 / ANYONECANPAY 0xC1): - anf_lower.zig swaps the hardcoded 0x41 preimage-type assert for the declared mode and threads the flag onto each check_preimage (auto-injected or manual) via CheckPreimage.sighash_flag; default omits it (zero golden churn, 64/64 conformance goldens pass). - stack_lower.zig makes the OP_PUSH_TX binding blob flag-parametric (the single 0141 push -> 01<flag>); default returns the frozen constant. - ABIMethod.sigHashType (emit.zig) carries a non-default mode so the SDK builds a matching preimage; ANFMethod.sighash_type is the in-memory carrier preserved across constant_fold + ec_optimizer. - check_preimage sighashFlag emitted into the ANF JSON (artifact + canonical) only when non-default; json.zig loads it for --ir replay. Parity proven: SINGLE|FORKID -> sighash byte 0x43, ALL|ANYONECANPAY|FORKID -> 0xC1, ALL|FORKID == no directive (byte-identical to TS); each of the 5 unsound cases rejected with a diagnostic.
…_SIG (#106) #123 — deployment-SDK sighash threading: - ABIMethod.sigHashType read from the artifact JSON (absent = 0x41). - OpPushTx.preimage / RawTx.sighashBIP143 fixed to BIP-143 exactly (kept in lock step): hashSequence committed only under pure SIGHASH_ALL, and SINGLE hashOutputs = hash256(output[inputIndex]) (or ZERO32 out of range) instead of digesting the whole set (F5). ANYONECANPAY still zeroes hashPrevouts; NONE still zeroes hashOutputs. - computePushTxSig gains a sigHashType param (drives the preimage flag + the appended DER byte). RunarContract resolves ABIMethod.sigHashType at every call/terminal path and threads it into the covenant preimage + derived signature + user-Sig sighash. Default 0x41 unchanged. #106 — EMPTY_SIG marker for OR-CHECKSIG branched auth: - RunarContract.EMPTY_SIG sentinel + isEmptySig; ContractScript encodes it as OP_0 ("00"). It is non-null, so the auto-signature collectors never sign it. call("execute", [null, EMPTY_SIG]) signs only slot 0. Verified: BIP-143 scope unit tests (SINGLE same-index, NONE/ACP zeroing, tail byte); full deploy/call preimage sighash byte 0xc1 (ACP) / 0x41 (default); EMPTY_SIG wire parity (failing branch is OP_0 under [null, EMPTY_SIG], non-empty under [null, null]). All 468 SDK tests green.
#123 SDK (translate af0c84d): - computeOpPushTxWithSigHash builds the BIP-143 preimage + derives the OP_PUSH_TX signature under a caller-supplied sighash type; bsvz's transaction/sighash.zig does the field zeroing natively (hashPrevouts under ANYONECANPAY, hashSequence unless pure ALL, hashOutputs under NONE / same-index SINGLE), so passing the flag is the complete port — no hand-rolled preimage zeroing needed (mirrors the Go tier's use of go-sdk's CalcInputPreimage). computeOpPushTx now delegates with the 0x41 default. - ABIMethod.sig_hash_type (sdk_types.zig) deserializes the artifact field; RunarContract.methodSigHashType resolves a method's declared mode by ABI lookup (default 0x41). Every OP_PUSH_TX call site on the call + terminal stateful paths threads it, so a @sigHash SINGLE|FORKID method's preimage carries sighash byte 0x43 and an ANYONECANPAY method 0xC1, default unchanged. #106 EmptySig (translate 838c315): - StateValue.empty_sig marker + EMPTY_SIG constant; encodeArg emits OP_0 for it; it is NOT the .int=0 auto-sign sentinel so the four auto-sign collectors skip it (falls through to arg.clone, never added to sig_indices, never signed). OR-CHECKSIG [realSig, EmptySig] encodes <push sig> || 00 — wire-byte parity with the TS SDK. Purely additive: existing default-sighash methods and non-empty-sig callers are byte-identical. bsvz SINGLE same-index hashOutputs verified via preimage divergence tests.
…ross all 7 tiers The author-facing comment directives `@sighash <FLAGS>` (#123, per-method sighash type) and `@embedAlways` (#109, readonly-field DCE opt-out) are honoured ONLY on the `.runar.ts` surface, whose parser reads leading trivia. The other eight surface parsers ignore comments, so a directive there would be silently dropped and change signing / DCE semantics. Go, Zig, Ruby, and Java already fail closed on the non-.runar.ts formats. Python and Rust had fully REMOVED their guards (silently ignoring the directives), and the TypeScript reference never had one. Restore a narrowed, word-boundary-anchored (`@sighash\b` / `@embedAlways\b`, so `sighashType` does not trip it) fail-closed guard in those three tiers: error on any non-.runar.ts format, keep honouring the directives on `.runar.ts`. No conformance fixture uses either directive, so Stack-IR / hex parity and the parser-only matrix are unaffected. One guard test per changed tier covers a non-.runar.ts @sigHash + @embedAlways rejection, a .runar.ts positive, and a word-boundary negative.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port
@sighash/@embedAlways/ EmptySig to the remaining 6 tiers (completes #109, #123, #106)Follow-up to #136. That PR landed the two author-facing directives (
/** @sighash <FLAGS> */,/** @embedAlways */) and the EmptySig marker at the TypeScript reference tier, with the other six compilers fail-closed on the directives. This PR ports all three to Go, Rust, Python, Zig, Ruby, Java — byte-identical to the TS reference and, for@sighash, replicating the security-hardened field-usage validation in every tier — so the features now work across all seven tiers.What's ported (each tier, byte-parity with TS)
@sighash— per-method sighash declaration with mode-aware field-usage validation (the security core) in all 7 tiers: the 5 rejection rules + the hardening from Remediate 14 of 15 open issues: 9 correctness bugs across all 7 tiers + design features #136's adversarial audit (SINGLE mutate-only continuation rejected as value-skimmable; FORKID required; the field-read walk covers loop headers + assignment targets;requireOutputP2PKHrejected under SINGLE; SINGLEhashOutputs= same-index output only). Codegen swaps the injected0x41for the declared mode and makes the OP_PUSH_TX binding-blob flag byte parametric; the SDK threads the mode through the BIP-143 preimage. Default (no directive) stays0x41, byte-identical.@embedAlways— readonly-field DCE opt-out via reference-injection, + the compile warning, in all 7 tiers.Directive-guard consistency
The directives are honored only on the
.runar.tssurface (matching the reference); every tier — now including TS — fails closed (errors) on a directive found in any of the other 8 formats, rather than silently ignoring it. Full 9-format directive parsing is a possible future enhancement.Per-tier parity & security
Every tier verified byte-identical to the TS compiler:
@sighash SINGLE|FORKID→ sighash byte0x43,ANYONECANPAY|ALL|FORKID→0xC1, default →0x41;@embedAlwaysfield preserved. Each tier has one rejection test per unsound-usage rule. EmptySig wire parity confirmed per tier ([real, EmptySig]→ OP_0 in the failing branch). Ports also caught and fixed real per-tier bugs (e.g. a Python constant-fold pass that droppedsighashType).Not fully cross-tier (documented, per project policy)
@sighashon-chain execution correctness is proven at the wire-byte level in the Zig/Ruby/Java SDKs (no ScriptVM per project policy); the TS/Go/Rust/Python paths replay through an interpreter.@embedAlwayscompile warning is omitted in the Ruby tier (no diagnostics surface for it) — a documented no-op divergence.Verification (full matrix, all green)
@sighash/@embedAlwayscontracts (0x43 / 0xC1 / 0x41; field preserved) + one rejection test per security rule per tier; EmptySig wire parity per SDK.