@opcat-labs/opcat 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @opcat-labs/opcat
2
2
 
3
+ ## 2.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - refactor: reuse Interpreter methods for checkDataSig and add signData utility
8
+ - Refactor checkDataSig.ts to use Interpreter.checkDataSigSignatureEncoding() and checkPubkeyEncoding()
9
+ - Add signData(privateKey, message) utility for Oracle scenarios
10
+ - Add signDataWithInternalKey() using INTERNAL_KEY from ContextUtils
11
+ - Add end-to-end contract tests for CheckDataSig
12
+ - Add OP_CHECKSIGFROMSTACK and OP_CHECKSIGFROMSTACKVERIFY opcode tests
13
+ - Add TypeScript type declarations for checkDataSigSignatureEncoding and isDER
14
+
3
15
  ## 2.1.2
4
16
 
5
17
  ### Patch Changes
@@ -8,6 +8,7 @@ var BN = require('../crypto/bn.cjs');
8
8
  var Hash = require('../crypto/hash.cjs');
9
9
  var Signature = require('../crypto/signature.cjs');
10
10
  var PublicKey = require('../publickey.cjs');
11
+ var ECDSA = require('../crypto/ecdsa.cjs');
11
12
  var Stack = require('./stack.cjs');
12
13
  var Transaction = require('../transaction/index.cjs');
13
14
 
@@ -443,6 +444,118 @@ Interpreter.prototype.checkPubkeyEncoding = function (buf) {
443
444
  return true;
444
445
  };
445
446
 
447
+
448
+ /**
449
+ * Validates pure DER signature encoding (without sighash type).
450
+ * Used for OP_CHECKSIGFROMSTACK which expects signatures without trailing sighash byte.
451
+ * @param {Buffer} buf - The buffer containing the signature to verify
452
+ * @returns {boolean} True if the signature is valid DER-encoded, false otherwise
453
+ * @static
454
+ */
455
+ Interpreter.isDER = function (buf) {
456
+ if (buf.length < 8) {
457
+ // Non-canonical signature: too short (min DER sig is 8 bytes)
458
+ return false;
459
+ }
460
+ if (buf.length > 72) {
461
+ // Non-canonical signature: too long (max DER sig is 72 bytes without sighash)
462
+ return false;
463
+ }
464
+ if (buf[0] !== 0x30) {
465
+ // Non-canonical signature: wrong type
466
+ return false;
467
+ }
468
+ if (buf[1] !== buf.length - 2) {
469
+ // Non-canonical signature: wrong length marker (for pure DER, length = buf.length - 2)
470
+ return false;
471
+ }
472
+ var nLenR = buf[3];
473
+ if (5 + nLenR >= buf.length) {
474
+ // Non-canonical signature: S length misplaced
475
+ return false;
476
+ }
477
+ var nLenS = buf[5 + nLenR];
478
+ if (nLenR + nLenS + 6 !== buf.length) {
479
+ // Non-canonical signature: R+S length mismatch (for pure DER, total = R + S + 6)
480
+ return false;
481
+ }
482
+
483
+ var R = buf.slice(4);
484
+ if (buf[4 - 2] !== 0x02) {
485
+ // Non-canonical signature: R value type mismatch
486
+ return false;
487
+ }
488
+ if (nLenR === 0) {
489
+ // Non-canonical signature: R length is zero
490
+ return false;
491
+ }
492
+ if (R[0] & 0x80) {
493
+ // Non-canonical signature: R value negative
494
+ return false;
495
+ }
496
+ if (nLenR > 1 && R[0] === 0x00 && !(R[1] & 0x80)) {
497
+ // Non-canonical signature: R value excessively padded
498
+ return false;
499
+ }
500
+
501
+ var S = buf.slice(6 + nLenR);
502
+ if (buf[6 + nLenR - 2] !== 0x02) {
503
+ // Non-canonical signature: S value type mismatch
504
+ return false;
505
+ }
506
+ if (nLenS === 0) {
507
+ // Non-canonical signature: S length is zero
508
+ return false;
509
+ }
510
+ if (S[0] & 0x80) {
511
+ // Non-canonical signature: S value negative
512
+ return false;
513
+ }
514
+ if (nLenS > 1 && S[0] === 0x00 && !(S[1] & 0x80)) {
515
+ // Non-canonical signature: S value excessively padded
516
+ return false;
517
+ }
518
+ return true;
519
+ };
520
+
521
+
522
+ /**
523
+ * Checks if a signature encoding is valid for OP_CHECKSIGFROMSTACK.
524
+ * Unlike checkSignatureEncoding, this expects pure DER signatures without sighash type.
525
+ * @param {Buffer} buf - The signature buffer to validate
526
+ * @returns {boolean} True if valid, false otherwise (sets errstr on failure)
527
+ */
528
+ Interpreter.prototype.checkDataSigSignatureEncoding = function (buf) {
529
+ var sig;
530
+
531
+ // Empty signature is allowed
532
+ if (buf.length === 0) {
533
+ return true;
534
+ }
535
+
536
+ // For OP_CHECKSIGFROMSTACK, use pure DER validation (no sighash type)
537
+ if (
538
+ (this.flags &
539
+ (Interpreter.SCRIPT_VERIFY_DERSIG |
540
+ Interpreter.SCRIPT_VERIFY_LOW_S |
541
+ Interpreter.SCRIPT_VERIFY_STRICTENC)) !==
542
+ 0 &&
543
+ !Interpreter.isDER(buf)
544
+ ) {
545
+ this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT';
546
+ return false;
547
+ } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) {
548
+ sig = Signature.fromDER(buf);
549
+ if (!sig.hasLowS()) {
550
+ this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S';
551
+ return false;
552
+ }
553
+ }
554
+ // Note: No STRICTENC hashtype check for OP_CHECKSIGFROMSTACK since it has no sighash type
555
+
556
+ return true
557
+ };
558
+
446
559
  /**
447
560
  * Checks if a buffer is minimally encoded (see https://github.com/bitcoincashorg/spec/blob/master/may-2018-reenabled-opcodes.md#op_bin2num) as a number.
448
561
  * @param {Buffer} buf - The buffer to check.
@@ -1979,6 +2092,65 @@ Interpreter.prototype.step = function (scriptType) {
1979
2092
  }
1980
2093
  break;
1981
2094
 
2095
+ case Opcode.OP_CHECKSIGFROMSTACK:
2096
+ case Opcode.OP_CHECKSIGFROMSTACKVERIFY:
2097
+ // Stack order (bottom to top): <sig> <msg> <pubKey>
2098
+ // (sig msg pubkey -- bool) for OP_CHECKSIGFROMSTACK
2099
+ // (sig msg pubkey -- ) for OP_CHECKSIGFROMSTACKVERIFY
2100
+ // Note: Unlike OP_CHECKSIG, signatures are pure DER without sighash type
2101
+ if (this.stack.length < 3) {
2102
+ this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
2103
+ return false;
2104
+ }
2105
+
2106
+ bufPubkey = stacktop(-1); // pubKey at top
2107
+ var bufMsg = stacktop(-2); // msg in middle
2108
+ bufSig = stacktop(-3); // sig at bottom
2109
+
2110
+ // Use checkDataSigSignatureEncoding for pure DER validation (no sighash type)
2111
+ if (!this.checkDataSigSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) {
2112
+ return false;
2113
+ }
2114
+
2115
+ fSuccess = false;
2116
+ try {
2117
+ // Compute SHA256 of msg (single hash, not double)
2118
+ // Reverse to little-endian format (same as checkSig) for signature verification
2119
+ var hashbuf = Hash.sha256(bufMsg).reverse();
2120
+
2121
+ // Use fromDER for pure DER signatures (no sighash type)
2122
+ sig = Signature.fromDER(bufSig);
2123
+ pubkey = PublicKey.fromBuffer(bufPubkey, false);
2124
+
2125
+ // Verify signature using ECDSA with little endian
2126
+ fSuccess = ECDSA.verify(hashbuf, sig, pubkey, 'little');
2127
+ } catch (e) {
2128
+ // invalid sig or pubkey
2129
+ fSuccess = false;
2130
+ }
2131
+
2132
+ if (!fSuccess && this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL && bufSig.length) {
2133
+ this.errstr = 'SCRIPT_ERR_NULLFAIL';
2134
+ return false;
2135
+ }
2136
+
2137
+ // Pop all 3 elements
2138
+ this.stack.pop();
2139
+ this.stack.pop();
2140
+ this.stack.pop();
2141
+
2142
+ if (opcodenum === Opcode.OP_CHECKSIGFROMSTACKVERIFY) {
2143
+ if (!fSuccess) {
2144
+ this.errstr = 'SCRIPT_ERR_CHECKSIGFROMSTACKVERIFY';
2145
+ return false;
2146
+ }
2147
+ // VERIFY variant doesn't push anything on success
2148
+ } else {
2149
+ // Push result for OP_CHECKSIGFROMSTACK
2150
+ this.stack.push(fSuccess ? Interpreter.getTrue() : Interpreter.getFalse());
2151
+ }
2152
+ break;
2153
+
1982
2154
  default:
1983
2155
  this.errstr = 'SCRIPT_ERR_BAD_OPCODE';
1984
2156
  return false;
package/cjs/opcode.cjs CHANGED
@@ -264,6 +264,10 @@ Opcode.map = {
264
264
  OP_CHECKLOCKTIMEVERIFY: 177,
265
265
  OP_CHECKSEQUENCEVERIFY: 178,
266
266
 
267
+ // OPCAT signature from stack
268
+ OP_CHECKSIGFROMSTACK: 186, // 0xba
269
+ OP_CHECKSIGFROMSTACKVERIFY: 187, // 0xbb
270
+
267
271
  // expansion
268
272
  OP_NOP1: 176,
269
273
  OP_NOP2: 177,
@@ -7,6 +7,7 @@ import BN from '../crypto/bn.js';
7
7
  import Hash from '../crypto/hash.js';
8
8
  import Signature from '../crypto/signature.js';
9
9
  import PublicKey from '../publickey.js';
10
+ import ECDSA from '../crypto/ecdsa.js';
10
11
  import Stack from './stack.js';
11
12
  import Transaction from '../transaction/index.js';
12
13
 
@@ -442,6 +443,118 @@ Interpreter.prototype.checkPubkeyEncoding = function (buf) {
442
443
  return true;
443
444
  };
444
445
 
446
+
447
+ /**
448
+ * Validates pure DER signature encoding (without sighash type).
449
+ * Used for OP_CHECKSIGFROMSTACK which expects signatures without trailing sighash byte.
450
+ * @param {Buffer} buf - The buffer containing the signature to verify
451
+ * @returns {boolean} True if the signature is valid DER-encoded, false otherwise
452
+ * @static
453
+ */
454
+ Interpreter.isDER = function (buf) {
455
+ if (buf.length < 8) {
456
+ // Non-canonical signature: too short (min DER sig is 8 bytes)
457
+ return false;
458
+ }
459
+ if (buf.length > 72) {
460
+ // Non-canonical signature: too long (max DER sig is 72 bytes without sighash)
461
+ return false;
462
+ }
463
+ if (buf[0] !== 0x30) {
464
+ // Non-canonical signature: wrong type
465
+ return false;
466
+ }
467
+ if (buf[1] !== buf.length - 2) {
468
+ // Non-canonical signature: wrong length marker (for pure DER, length = buf.length - 2)
469
+ return false;
470
+ }
471
+ var nLenR = buf[3];
472
+ if (5 + nLenR >= buf.length) {
473
+ // Non-canonical signature: S length misplaced
474
+ return false;
475
+ }
476
+ var nLenS = buf[5 + nLenR];
477
+ if (nLenR + nLenS + 6 !== buf.length) {
478
+ // Non-canonical signature: R+S length mismatch (for pure DER, total = R + S + 6)
479
+ return false;
480
+ }
481
+
482
+ var R = buf.slice(4);
483
+ if (buf[4 - 2] !== 0x02) {
484
+ // Non-canonical signature: R value type mismatch
485
+ return false;
486
+ }
487
+ if (nLenR === 0) {
488
+ // Non-canonical signature: R length is zero
489
+ return false;
490
+ }
491
+ if (R[0] & 0x80) {
492
+ // Non-canonical signature: R value negative
493
+ return false;
494
+ }
495
+ if (nLenR > 1 && R[0] === 0x00 && !(R[1] & 0x80)) {
496
+ // Non-canonical signature: R value excessively padded
497
+ return false;
498
+ }
499
+
500
+ var S = buf.slice(6 + nLenR);
501
+ if (buf[6 + nLenR - 2] !== 0x02) {
502
+ // Non-canonical signature: S value type mismatch
503
+ return false;
504
+ }
505
+ if (nLenS === 0) {
506
+ // Non-canonical signature: S length is zero
507
+ return false;
508
+ }
509
+ if (S[0] & 0x80) {
510
+ // Non-canonical signature: S value negative
511
+ return false;
512
+ }
513
+ if (nLenS > 1 && S[0] === 0x00 && !(S[1] & 0x80)) {
514
+ // Non-canonical signature: S value excessively padded
515
+ return false;
516
+ }
517
+ return true;
518
+ };
519
+
520
+
521
+ /**
522
+ * Checks if a signature encoding is valid for OP_CHECKSIGFROMSTACK.
523
+ * Unlike checkSignatureEncoding, this expects pure DER signatures without sighash type.
524
+ * @param {Buffer} buf - The signature buffer to validate
525
+ * @returns {boolean} True if valid, false otherwise (sets errstr on failure)
526
+ */
527
+ Interpreter.prototype.checkDataSigSignatureEncoding = function (buf) {
528
+ var sig;
529
+
530
+ // Empty signature is allowed
531
+ if (buf.length === 0) {
532
+ return true;
533
+ }
534
+
535
+ // For OP_CHECKSIGFROMSTACK, use pure DER validation (no sighash type)
536
+ if (
537
+ (this.flags &
538
+ (Interpreter.SCRIPT_VERIFY_DERSIG |
539
+ Interpreter.SCRIPT_VERIFY_LOW_S |
540
+ Interpreter.SCRIPT_VERIFY_STRICTENC)) !==
541
+ 0 &&
542
+ !Interpreter.isDER(buf)
543
+ ) {
544
+ this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT';
545
+ return false;
546
+ } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) {
547
+ sig = Signature.fromDER(buf);
548
+ if (!sig.hasLowS()) {
549
+ this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S';
550
+ return false;
551
+ }
552
+ }
553
+ // Note: No STRICTENC hashtype check for OP_CHECKSIGFROMSTACK since it has no sighash type
554
+
555
+ return true;
556
+ };
557
+
445
558
  /**
446
559
  * Checks if a buffer is minimally encoded (see https://github.com/bitcoincashorg/spec/blob/master/may-2018-reenabled-opcodes.md#op_bin2num) as a number.
447
560
  * @param {Buffer} buf - The buffer to check.
@@ -1978,6 +2091,65 @@ Interpreter.prototype.step = function (scriptType) {
1978
2091
  }
1979
2092
  break;
1980
2093
 
2094
+ case Opcode.OP_CHECKSIGFROMSTACK:
2095
+ case Opcode.OP_CHECKSIGFROMSTACKVERIFY:
2096
+ // Stack order (bottom to top): <sig> <msg> <pubKey>
2097
+ // (sig msg pubkey -- bool) for OP_CHECKSIGFROMSTACK
2098
+ // (sig msg pubkey -- ) for OP_CHECKSIGFROMSTACKVERIFY
2099
+ // Note: Unlike OP_CHECKSIG, signatures are pure DER without sighash type
2100
+ if (this.stack.length < 3) {
2101
+ this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION';
2102
+ return false;
2103
+ }
2104
+
2105
+ bufPubkey = stacktop(-1); // pubKey at top
2106
+ var bufMsg = stacktop(-2); // msg in middle
2107
+ bufSig = stacktop(-3); // sig at bottom
2108
+
2109
+ // Use checkDataSigSignatureEncoding for pure DER validation (no sighash type)
2110
+ if (!this.checkDataSigSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) {
2111
+ return false;
2112
+ }
2113
+
2114
+ fSuccess = false;
2115
+ try {
2116
+ // Compute SHA256 of msg (single hash, not double)
2117
+ // Reverse to little-endian format (same as checkSig) for signature verification
2118
+ var hashbuf = Hash.sha256(bufMsg).reverse();
2119
+
2120
+ // Use fromDER for pure DER signatures (no sighash type)
2121
+ sig = Signature.fromDER(bufSig);
2122
+ pubkey = PublicKey.fromBuffer(bufPubkey, false);
2123
+
2124
+ // Verify signature using ECDSA with little endian
2125
+ fSuccess = ECDSA.verify(hashbuf, sig, pubkey, 'little');
2126
+ } catch (e) {
2127
+ // invalid sig or pubkey
2128
+ fSuccess = false;
2129
+ }
2130
+
2131
+ if (!fSuccess && this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL && bufSig.length) {
2132
+ this.errstr = 'SCRIPT_ERR_NULLFAIL';
2133
+ return false;
2134
+ }
2135
+
2136
+ // Pop all 3 elements
2137
+ this.stack.pop();
2138
+ this.stack.pop();
2139
+ this.stack.pop();
2140
+
2141
+ if (opcodenum === Opcode.OP_CHECKSIGFROMSTACKVERIFY) {
2142
+ if (!fSuccess) {
2143
+ this.errstr = 'SCRIPT_ERR_CHECKSIGFROMSTACKVERIFY';
2144
+ return false;
2145
+ }
2146
+ // VERIFY variant doesn't push anything on success
2147
+ } else {
2148
+ // Push result for OP_CHECKSIGFROMSTACK
2149
+ this.stack.push(fSuccess ? Interpreter.getTrue() : Interpreter.getFalse());
2150
+ }
2151
+ break;
2152
+
1981
2153
  default:
1982
2154
  this.errstr = 'SCRIPT_ERR_BAD_OPCODE';
1983
2155
  return false;
package/esm/opcode.js CHANGED
@@ -264,6 +264,10 @@ Opcode.map = {
264
264
  OP_CHECKLOCKTIMEVERIFY: 177,
265
265
  OP_CHECKSEQUENCEVERIFY: 178,
266
266
 
267
+ // OPCAT signature from stack
268
+ OP_CHECKSIGFROMSTACK: 186, // 0xba
269
+ OP_CHECKSIGFROMSTACKVERIFY: 187, // 0xbb
270
+
267
271
  // expansion
268
272
  OP_NOP1: 176,
269
273
  OP_NOP2: 177,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opcat-labs/opcat",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "opcat base SDK",
5
5
  "main": "./cjs/index.cjs",
6
6
  "module": "./esm/index.js",
package/test/opcode.cjs CHANGED
@@ -83,8 +83,9 @@ describe('Opcode', function () {
83
83
  });
84
84
 
85
85
  describe('@map', function () {
86
- it('should have a map containing 118 elements', function () {
87
- Object.keys(Opcode.map).length.should.equal(118);
86
+ it('should have a map containing 120 elements', function () {
87
+ // 118 base opcodes + 2 new CHECKSIGFROMSTACK opcodes
88
+ Object.keys(Opcode.map).length.should.equal(120);
88
89
  });
89
90
  });
90
91
 
@@ -731,4 +731,173 @@ describe('Interpreter', function () {
731
731
  testTxs(txValid, true);
732
732
  //testTxs(txInvalid, false);
733
733
  });
734
+
735
+ describe('OP_CHECKSIGFROMSTACK', function () {
736
+ var crypto = opcat.crypto;
737
+ var Hash = crypto.Hash;
738
+ var ECDSA = crypto.ECDSA;
739
+
740
+ it('should verify a valid signature with OP_CHECKSIGFROMSTACK', function () {
741
+ // Generate keypair
742
+ var privKey = opcat.PrivateKey.fromRandom();
743
+ var pubKey = privKey.toPublicKey();
744
+
745
+ // Message to sign
746
+ var message = Buffer.from('Test message for OP_CHECKSIGFROMSTACK', 'utf8');
747
+
748
+ // Hash the message with SHA256 (single hash) and reverse for little-endian
749
+ // The BVM does sha256(msg).reverse() before ECDSA.verify
750
+ var msgHash = Hash.sha256(message).reverse();
751
+
752
+ // Sign the hash
753
+ var signature = ECDSA.sign(msgHash, privKey, 'little');
754
+
755
+ // Use pure DER signature (NO sighash type for OP_CHECKSIGFROMSTACK)
756
+ var sigDER = signature.toDER();
757
+
758
+ // Build script: <sig> <msg> <pubkey> OP_CHECKSIGFROMSTACK
759
+ var scriptSig = new Script()
760
+ .add(sigDER)
761
+ .add(message)
762
+ .add(pubKey.toBuffer());
763
+
764
+ var scriptPubkey = new Script()
765
+ .add(Opcode.OP_CHECKSIGFROMSTACK);
766
+
767
+ // Create interpreter and verify
768
+ var interp = new Interpreter();
769
+ var result = interp.verify(scriptSig, scriptPubkey);
770
+
771
+ result.should.equal(true);
772
+ interp.errstr.should.equal('');
773
+ });
774
+
775
+ it('should fail with invalid signature using OP_CHECKSIGFROMSTACK', function () {
776
+ // Generate two keypairs
777
+ var privKey = opcat.PrivateKey.fromRandom();
778
+ var pubKey = privKey.toPublicKey();
779
+ var wrongPrivKey = opcat.PrivateKey.fromRandom();
780
+
781
+ // Message to sign
782
+ var message = Buffer.from('Test message', 'utf8');
783
+
784
+ // Hash the message with SHA256 and reverse for little-endian
785
+ var msgHash = Hash.sha256(message).reverse();
786
+
787
+ // Sign with wrong private key - use pure DER (no sighash type)
788
+ var signature = ECDSA.sign(msgHash, wrongPrivKey, 'little');
789
+ var sigDER = signature.toDER();
790
+
791
+ // Build script with correct pubkey but wrong signature
792
+ var scriptSig = new Script()
793
+ .add(sigDER)
794
+ .add(message)
795
+ .add(pubKey.toBuffer());
796
+
797
+ var scriptPubkey = new Script()
798
+ .add(Opcode.OP_CHECKSIGFROMSTACK);
799
+
800
+ var interp = new Interpreter();
801
+ var result = interp.verify(scriptSig, scriptPubkey);
802
+
803
+ result.should.equal(false);
804
+ });
805
+
806
+ it('should verify with OP_CHECKSIGFROMSTACKVERIFY and continue execution', function () {
807
+ // Generate keypair
808
+ var privKey = opcat.PrivateKey.fromRandom();
809
+ var pubKey = privKey.toPublicKey();
810
+
811
+ // Message to sign
812
+ var message = Buffer.from('VERIFY test', 'utf8');
813
+ var msgHash = Hash.sha256(message).reverse();
814
+ var signature = ECDSA.sign(msgHash, privKey, 'little');
815
+ // Use pure DER signature (no sighash type)
816
+ var sigDER = signature.toDER();
817
+
818
+ // Build script: <sig> <msg> <pubkey> OP_CHECKSIGFROMSTACKVERIFY OP_1
819
+ var scriptSig = new Script()
820
+ .add(sigDER)
821
+ .add(message)
822
+ .add(pubKey.toBuffer());
823
+
824
+ var scriptPubkey = new Script()
825
+ .add(Opcode.OP_CHECKSIGFROMSTACKVERIFY)
826
+ .add(Opcode.OP_1);
827
+
828
+ var interp = new Interpreter();
829
+ var result = interp.verify(scriptSig, scriptPubkey);
830
+
831
+ result.should.equal(true);
832
+ });
833
+
834
+ it('should fail OP_CHECKSIGFROMSTACKVERIFY with invalid signature', function () {
835
+ // Generate keypair
836
+ var privKey = opcat.PrivateKey.fromRandom();
837
+ var pubKey = privKey.toPublicKey();
838
+ var wrongPrivKey = opcat.PrivateKey.fromRandom();
839
+
840
+ var message = Buffer.from('VERIFY fail test', 'utf8');
841
+ var msgHash = Hash.sha256(message).reverse();
842
+ var signature = ECDSA.sign(msgHash, wrongPrivKey, 'little');
843
+ // Use pure DER signature (no sighash type)
844
+ var sigDER = signature.toDER();
845
+
846
+ var scriptSig = new Script()
847
+ .add(sigDER)
848
+ .add(message)
849
+ .add(pubKey.toBuffer());
850
+
851
+ var scriptPubkey = new Script()
852
+ .add(Opcode.OP_CHECKSIGFROMSTACKVERIFY)
853
+ .add(Opcode.OP_1);
854
+
855
+ var interp = new Interpreter();
856
+ var result = interp.verify(scriptSig, scriptPubkey);
857
+
858
+ result.should.equal(false);
859
+ interp.errstr.should.equal('SCRIPT_ERR_CHECKSIGFROMSTACKVERIFY');
860
+ });
861
+
862
+ it('should fail with insufficient stack elements', function () {
863
+ // Only provide 2 elements instead of 3
864
+ var scriptSig = new Script()
865
+ .add(Buffer.from('signature', 'utf8'))
866
+ .add(Buffer.from('message', 'utf8'));
867
+
868
+ var scriptPubkey = new Script()
869
+ .add(Opcode.OP_CHECKSIGFROMSTACK);
870
+
871
+ var interp = new Interpreter();
872
+ var result = interp.verify(scriptSig, scriptPubkey);
873
+
874
+ result.should.equal(false);
875
+ interp.errstr.should.equal('SCRIPT_ERR_INVALID_STACK_OPERATION');
876
+ });
877
+
878
+ it('should work with empty message', function () {
879
+ var privKey = opcat.PrivateKey.fromRandom();
880
+ var pubKey = privKey.toPublicKey();
881
+
882
+ // Empty message
883
+ var message = Buffer.from('', 'utf8');
884
+ var msgHash = Hash.sha256(message).reverse();
885
+ var signature = ECDSA.sign(msgHash, privKey, 'little');
886
+ // Use pure DER signature (no sighash type)
887
+ var sigDER = signature.toDER();
888
+
889
+ var scriptSig = new Script()
890
+ .add(sigDER)
891
+ .add(message)
892
+ .add(pubKey.toBuffer());
893
+
894
+ var scriptPubkey = new Script()
895
+ .add(Opcode.OP_CHECKSIGFROMSTACK);
896
+
897
+ var interp = new Interpreter();
898
+ var result = interp.verify(scriptSig, scriptPubkey);
899
+
900
+ result.should.equal(true);
901
+ });
902
+ });
734
903
  });
@@ -871,7 +871,8 @@ describe('Script', function () {
871
871
 
872
872
  describe('#add and #prepend', function () {
873
873
  it('should add these ops', function () {
874
- Script().add(1).add(10).add(186).toString().should.equal('1 0x0a 0xba');
874
+ // 186 (0xba) is now OP_CHECKSIGFROMSTACK
875
+ Script().add(1).add(10).add(186).toString().should.equal('1 0x0a OP_CHECKSIGFROMSTACK');
875
876
  Script().add(Buffer.from('03e8', 'hex')).toString().should.equal('2 0x03e8');
876
877
  Script().add('OP_CHECKMULTISIG').toString().should.equal('OP_CHECKMULTISIG');
877
878
  Script().add('OP_1').add('OP_2').toString().should.equal('OP_1 OP_2');
@@ -894,6 +895,15 @@ describe('Script', function () {
894
895
  .should.equal('OP_4 OP_2 OP_1 OP_3');
895
896
  });
896
897
 
898
+ it('should recognize OP_CHECKSIGFROMSTACK and OP_CHECKSIGFROMSTACKVERIFY opcodes', function () {
899
+ // Test adding by opcode number
900
+ Script().add(186).toString().should.equal('OP_CHECKSIGFROMSTACK');
901
+ Script().add(187).toString().should.equal('OP_CHECKSIGFROMSTACKVERIFY');
902
+ // Test adding by opcode name
903
+ Script().add('OP_CHECKSIGFROMSTACK').toString().should.equal('OP_CHECKSIGFROMSTACK');
904
+ Script().add('OP_CHECKSIGFROMSTACKVERIFY').toString().should.equal('OP_CHECKSIGFROMSTACKVERIFY');
905
+ });
906
+
897
907
  it('should add these push data', function () {
898
908
  var buf = Buffer.alloc(1);
899
909
  buf.fill(0);
@@ -118,6 +118,13 @@ declare class Interpreter {
118
118
  * @returns {boolean} True if valid, false otherwise (with error string set).
119
119
  */
120
120
  checkPubkeyEncoding(buf: Buffer): boolean;
121
+ /**
122
+ * Checks if a signature encoding is valid for OP_CHECKSIGFROMSTACK.
123
+ * Unlike checkSignatureEncoding, this expects pure DER signatures without sighash type.
124
+ * @param {Buffer} buf - The signature buffer to validate
125
+ * @returns {boolean} True if valid, false otherwise (sets errstr on failure)
126
+ */
127
+ checkDataSigSignatureEncoding(buf: Buffer): boolean;
121
128
  /**
122
129
  * Evaluates a script by executing each opcode step-by-step.
123
130
  * Performs size checks on the script and stacks before execution.
@@ -169,6 +176,13 @@ declare class Interpreter {
169
176
  declare namespace Interpreter {
170
177
  function getTrue(): Buffer;
171
178
  function getFalse(): Buffer;
179
+ /**
180
+ * Validates pure DER signature encoding (without sighash type).
181
+ * Used for OP_CHECKSIGFROMSTACK which expects signatures without trailing sighash byte.
182
+ * @param {Buffer} buf - The buffer containing the signature to verify
183
+ * @returns {boolean} True if the signature is valid DER-encoded, false otherwise
184
+ */
185
+ function isDER(buf: Buffer): boolean;
172
186
  let MAX_SCRIPT_ELEMENT_SIZE: number;
173
187
  let MAXIMUM_ELEMENT_SIZE: number;
174
188
  let LOCKTIME_THRESHOLD: number;