@smartledger/bsv 8.3.1 → 9.1.0
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 +228 -0
- package/README.md +152 -1031
- package/STABILITY.md +134 -0
- package/bsv-gdaf.min.js +58 -53
- package/bsv-ltp.min.js +23 -18
- package/bsv-script-helper.min.js +1 -1
- package/bsv-smartcontract.min.js +21 -21
- package/bsv.bundle.js +58 -53
- package/bsv.d.ts +0 -4
- package/bsv.min.js +58 -53
- package/docs/BRC220_BATCH_LEAF_AMENDMENT.md +82 -7
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/README.md +2 -1
- package/docs/THREAT_MODEL.md +10 -2
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/docs/proposals/10.0.0-package-split.md +131 -0
- package/index.js +15 -26
- package/lib/block/merkleblock.js +19 -5
- package/lib/covenant/helpers.js +34 -35
- package/lib/covenant/pushtx.js +2 -2
- package/lib/custom-script-helper.js +0 -16
- package/lib/hdprivatekey.js +15 -0
- package/lib/ordinals/ordlock.js +1 -1
- package/lib/script/interpreter.js +109 -1
- package/lib/smart_contract/builder.js +14 -0
- package/lib/smart_contract/debugger.js +2 -2
- package/lib/smart_contract/dsl.js +2 -2
- package/lib/smart_contract/index.js +0 -3
- package/lib/smart_contract/locks.js +1 -41
- package/lib/smart_contract/pels.js +1 -1
- package/lib/smart_contract/token.js +1 -1
- package/lib/util/deprecate.js +159 -0
- package/package.json +19 -82
- package/tools/gen-brc220-batch-vector.js +205 -0
- package/version.js +1 -1
- package/.mocharc.json +0 -5
- package/test/address.js +0 -629
- package/test/block/block.js +0 -239
- package/test/block/blockheader.js +0 -270
- package/test/block/merkleblock.js +0 -207
- package/test/build/bundle_crypto_shim.js +0 -104
- package/test/build/bundle_externals.js +0 -125
- package/test/build/bundle_smoke.js +0 -55
- package/test/build/esbuild_main.js +0 -65
- package/test/build/esm_wrapper.js +0 -73
- package/test/build/exports_resolution.js +0 -84
- package/test/build/version_sync.js +0 -14
- package/test/cli/smoke.js +0 -261
- package/test/consensus/base58-vectors.js +0 -120
- package/test/consensus/sv-script-vectors.js +0 -81
- package/test/consensus/sv-sighash-vectors.js +0 -51
- package/test/consensus/sv-tx-vectors.js +0 -48
- package/test/credentials-test.js +0 -332
- package/test/crypto/backend_selection.js +0 -126
- package/test/crypto/bn.js +0 -168
- package/test/crypto/ecdsa.js +0 -426
- package/test/crypto/elliptic-fixed.js +0 -61
- package/test/crypto/hash.browser.js +0 -121
- package/test/crypto/hash.js +0 -122
- package/test/crypto/point.js +0 -193
- package/test/crypto/random.js +0 -105
- package/test/crypto/security.js +0 -176
- package/test/crypto/shamir.js +0 -177
- package/test/crypto/shamir_rngcap.js +0 -29
- package/test/crypto/signature.js +0 -399
- package/test/data/bip69.json +0 -215
- package/test/data/bitcoin-sv/README.md +0 -170
- package/test/data/bitcoin-sv/base58_encode_decode.json +0 -14
- package/test/data/bitcoin-sv/base58_keys_invalid.json +0 -152
- package/test/data/bitcoin-sv/base58_keys_valid.json +0 -452
- package/test/data/bitcoin-sv/script_tests.json +0 -2591
- package/test/data/bitcoin-sv/sighash.json +0 -1003
- package/test/data/bitcoin-sv/tx_invalid.json +0 -285
- package/test/data/bitcoin-sv/tx_valid.json +0 -367
- package/test/data/bitcoind/base58_keys_invalid.json +0 -152
- package/test/data/bitcoind/base58_keys_valid.json +0 -452
- package/test/data/bitcoind/blocks.json +0 -27
- package/test/data/bitcoind/script_tests.json +0 -2244
- package/test/data/bitcoind/sig_canonical.json +0 -7
- package/test/data/bitcoind/sig_noncanonical.json +0 -22
- package/test/data/bitcoind/tx_invalid.json +0 -177
- package/test/data/bitcoind/tx_valid.json +0 -224
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +0 -12
- package/test/data/blk86756-testnet.json +0 -684
- package/test/data/ecdsa.json +0 -230
- package/test/data/merkleblocks.js +0 -486
- package/test/data/messages.json +0 -22
- package/test/data/sighash.json +0 -1004
- package/test/data/tx_creation.json +0 -85
- package/test/didweb/relationships.js +0 -144
- package/test/ecies/bitcore-ecies.js +0 -178
- package/test/ecies/electrum-ecies.js +0 -206
- package/test/encoding/base58.js +0 -131
- package/test/encoding/base58check.js +0 -145
- package/test/encoding/bufferreader.js +0 -328
- package/test/encoding/bufferwriter.js +0 -160
- package/test/encoding/varint.js +0 -104
- package/test/gdaf/anchor_no_key_leak.js +0 -146
- package/test/gdaf/anchor_spv.js +0 -163
- package/test/gdaf/canonicalization.js +0 -106
- package/test/gdaf/canonicalize.js +0 -140
- package/test/gdaf/zk_prover.js +0 -204
- package/test/hdkeys.js +0 -365
- package/test/hdprivatekey.js +0 -339
- package/test/hdpublickey.js +0 -293
- package/test/index.js +0 -16
- package/test/ltp/ids.js +0 -74
- package/test/ltp/right.js +0 -198
- package/test/ltp/verify_failclosed.js +0 -134
- package/test/message/message.js +0 -190
- package/test/mnemonic/data/fixtures.json +0 -300
- package/test/mnemonic/mnemonic.js +0 -277
- package/test/mnemonic/mocha.opts +0 -1
- package/test/mnemonic/pbkdf2.test.js +0 -43
- package/test/networks.js +0 -207
- package/test/notaryhash/batch_leaf.js +0 -140
- package/test/notaryhash/certificate.js +0 -249
- package/test/notaryhash/encoding.js +0 -186
- package/test/notaryhash/interop.js +0 -112
- package/test/notaryhash/merkle.js +0 -181
- package/test/notaryhash/script.js +0 -270
- package/test/notaryhash/verify.js +0 -342
- package/test/opcode.js +0 -186
- package/test/ordinals/bsv20.js +0 -337
- package/test/ordinals/inscription.js +0 -319
- package/test/ordinals/ordlock.js +0 -613
- package/test/privatekey.js +0 -540
- package/test/publickey.js +0 -411
- package/test/regressions.js +0 -215
- package/test/script/chronicle.js +0 -543
- package/test/script/defaults.js +0 -160
- package/test/script/genesis_limits.js +0 -203
- package/test/script/interpreter.js +0 -776
- package/test/script/script.js +0 -1259
- package/test/script/string_ops.js +0 -88
- package/test/security/fail_closed_contracts.js +0 -104
- package/test/security/threat_model_coverage.js +0 -31
- package/test/smart_contract/covenants.js +0 -245
- package/test/smart_contract/dsl_debugger.js +0 -101
- package/test/smart_contract/extract_field.js +0 -61
- package/test/smart_contract/nonenforcing_guard.js +0 -44
- package/test/smart_contract/ordinal_transfer.js +0 -81
- package/test/smart_contract/preimage.js +0 -98
- package/test/smart_contract/sighash_marketplace.js +0 -108
- package/test/smart_contract/token_generalized.js +0 -185
- package/test/spv/headerchain.js +0 -86
- package/test/spv/merkleproof.js +0 -133
- package/test/statuslist/failclosed.js +0 -78
- package/test/transaction/deserialize.js +0 -33
- package/test/transaction/input/input.js +0 -92
- package/test/transaction/input/multisig.js +0 -174
- package/test/transaction/input/multisigscripthash.js +0 -111
- package/test/transaction/input/publickey.js +0 -68
- package/test/transaction/input/publickeyhash.js +0 -59
- package/test/transaction/output.js +0 -185
- package/test/transaction/sighash.js +0 -91
- package/test/transaction/signature.js +0 -127
- package/test/transaction/transaction.js +0 -1299
- package/test/transaction/unspentoutput.js +0 -97
- package/test/types/dts_drift.js +0 -124
- package/test/types/surface_honesty.js +0 -102
- package/test/util/id.js +0 -72
- package/test/util/js.js +0 -76
- package/test/util/preconditions.js +0 -79
- package/test/vcjwt/interop.js +0 -126
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
require('chai').should()
|
|
5
|
-
var bsv = require('../..')
|
|
6
|
-
var Interpreter = bsv.Script.Interpreter
|
|
7
|
-
var Script = bsv.Script
|
|
8
|
-
var Opcode = bsv.Opcode
|
|
9
|
-
var Transaction = bsv.Transaction
|
|
10
|
-
var CHRONICLE = Interpreter.SCRIPT_ENABLE_CHRONICLE
|
|
11
|
-
|
|
12
|
-
// OP_SUBSTR / OP_LEFT / OP_RIGHT — Chronicle string opcodes at 0xb3/0xb4/0xb5.
|
|
13
|
-
//
|
|
14
|
-
// Every case here passes SCRIPT_ENABLE_CHRONICLE, because without it these
|
|
15
|
-
// bytes are UPGRADABLE NOPS, exactly as they are on a pre-Chronicle node. An
|
|
16
|
-
// earlier version of these tests ran them with flags=0 and passed, which is
|
|
17
|
-
// what hid the fact that the interpreter was executing them unconditionally —
|
|
18
|
-
// more permissively than the network.
|
|
19
|
-
describe('String opcodes OP_SUBSTR / OP_LEFT / OP_RIGHT', function () {
|
|
20
|
-
var DATA = Buffer.from('aabbccdd', 'hex')
|
|
21
|
-
|
|
22
|
-
// Build `<DATA> <ops...>` and assert the top of stack equals `expected`.
|
|
23
|
-
function yields (buildOps, expectedHex) {
|
|
24
|
-
var lock = new Script().add(DATA)
|
|
25
|
-
buildOps(lock)
|
|
26
|
-
lock.add(Buffer.from(expectedHex, 'hex')).add(Opcode.OP_EQUAL)
|
|
27
|
-
var interp = new Interpreter()
|
|
28
|
-
return interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE)
|
|
29
|
-
}
|
|
30
|
-
function errors (buildOps) {
|
|
31
|
-
var lock = new Script().add(DATA)
|
|
32
|
-
buildOps(lock)
|
|
33
|
-
var interp = new Interpreter()
|
|
34
|
-
var ok
|
|
35
|
-
try { ok = interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE) } catch (e) { ok = false }
|
|
36
|
-
return ok === false
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
it('OP_LEFT keeps the first n bytes', function () {
|
|
40
|
-
yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_LEFT) }, 'aabb').should.equal(true)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('OP_RIGHT keeps the last n bytes', function () {
|
|
44
|
-
yields(function (s) { s.add(Opcode.OP_2).add(Opcode.OP_RIGHT) }, 'ccdd').should.equal(true)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('OP_RIGHT 0 yields the empty string (not the whole string)', function () {
|
|
48
|
-
yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_RIGHT) }, '').should.equal(true)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('OP_LEFT 0 yields the empty string', function () {
|
|
52
|
-
yields(function (s) { s.add(Opcode.OP_0).add(Opcode.OP_LEFT) }, '').should.equal(true)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('OP_SUBSTR extracts in[begin : begin+size]', function () {
|
|
56
|
-
// begin=1 size=2 -> bbcc. Stack order is (in begin size -- out).
|
|
57
|
-
yields(function (s) { s.add(Opcode.OP_1).add(Opcode.OP_2).add(Opcode.OP_SUBSTR) }, 'bbcc').should.equal(true)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// The node ERRORS rather than clamping:
|
|
61
|
-
// if(len < 0 || len > size) return SCRIPT_ERR_INVALID_NUMBER_RANGE;
|
|
62
|
-
// Clamping made scripts the node rejects succeed here.
|
|
63
|
-
it('OP_LEFT rejects n greater than the length', function () {
|
|
64
|
-
errors(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_LEFT) }).should.equal(true)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('OP_RIGHT rejects n greater than the length', function () {
|
|
68
|
-
errors(function (s) { s.add(Opcode.OP_10).add(Opcode.OP_RIGHT) }).should.equal(true)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('is a no-op without SCRIPT_ENABLE_CHRONICLE, as a pre-Chronicle node is', function () {
|
|
72
|
-
const interp = new Interpreter()
|
|
73
|
-
const lock = new Script().add(DATA).add(Opcode.OP_2).add(Opcode.OP_LEFT)
|
|
74
|
-
interp.verify(new Script(), lock, new Transaction(), 0, 0).should.equal(true)
|
|
75
|
-
// Untouched: the opcode did nothing, so both operands survive.
|
|
76
|
-
interp.stack.length.should.equal(2)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('rejects a negative size', function () {
|
|
80
|
-
errors(function (s) { s.add(Opcode.OP_1NEGATE).add(Opcode.OP_LEFT) }).should.equal(true)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('rejects OP_LEFT with too few stack items', function () {
|
|
84
|
-
var interp = new Interpreter()
|
|
85
|
-
var lock = new Script().add(Opcode.OP_LEFT) // nothing to operate on
|
|
86
|
-
interp.verify(new Script(), lock, new Transaction(), 0, CHRONICLE).should.equal(false)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// FAIL-CLOSED CONTRACTS — a mechanical guard for the operating principle that every
|
|
6
|
-
// security-critical verification "returns a bool or throws, never a chainable object"
|
|
7
|
-
// and that a forged/invalid input is REJECTED (never accepted).
|
|
8
|
-
//
|
|
9
|
-
// This exists because the v6.0.0 security-hardening pass fixed four CRITICAL fail-open
|
|
10
|
-
// forgery bugs, all of the same shape: code read a truthy return value (an ECDSA
|
|
11
|
-
// *instance*, a stub `{verified:true}`) as if it meant "valid". These tests assert the
|
|
12
|
-
// negative — a bad input must not verify — and pin the return-type contract so a
|
|
13
|
-
// regression to fail-open trips a red test instead of shipping.
|
|
14
|
-
|
|
15
|
-
require('chai').should()
|
|
16
|
-
var assert = require('assert')
|
|
17
|
-
var bsv = require('../..')
|
|
18
|
-
|
|
19
|
-
var Hash = bsv.crypto.Hash
|
|
20
|
-
var ECDSA = bsv.crypto.ECDSA
|
|
21
|
-
|
|
22
|
-
function isStrictBool (v) { return v === true || v === false }
|
|
23
|
-
|
|
24
|
-
describe('security: fail-closed verification contracts', function () {
|
|
25
|
-
// A signature over `hash` by `signer`, and a different key it was NOT signed by.
|
|
26
|
-
var signer = bsv.PrivateKey.fromRandom()
|
|
27
|
-
var hash = Hash.sha256(Buffer.from('the message'))
|
|
28
|
-
var goodSig = ECDSA.sign(hash, signer)
|
|
29
|
-
var rightPub = signer.toPublicKey()
|
|
30
|
-
var wrongPub = bsv.PrivateKey.fromRandom().toPublicKey()
|
|
31
|
-
|
|
32
|
-
describe('ECDSA signature verification', function () {
|
|
33
|
-
it('static ECDSA.verify returns a STRICT boolean (never a truthy object)', function () {
|
|
34
|
-
isStrictBool(ECDSA.verify(hash, goodSig, rightPub)).should.equal(true)
|
|
35
|
-
isStrictBool(ECDSA.verify(hash, goodSig, wrongPub)).should.equal(true)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('static ECDSA.verify accepts a valid signature and REJECTS a forged one', function () {
|
|
39
|
-
ECDSA.verify(hash, goodSig, rightPub).should.equal(true)
|
|
40
|
-
ECDSA.verify(hash, goodSig, wrongPub).should.equal(false) // forged: wrong key
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('instance verifyBool() is the safe boolean path and fails closed on a forgery', function () {
|
|
44
|
-
function inst (pub) {
|
|
45
|
-
var e = new ECDSA(); e.hashbuf = hash; e.sig = goodSig; e.pubkey = pub; return e
|
|
46
|
-
}
|
|
47
|
-
isStrictBool(inst(rightPub).verifyBool()).should.equal(true)
|
|
48
|
-
inst(rightPub).verifyBool().should.equal(true)
|
|
49
|
-
inst(wrongPub).verifyBool().should.equal(false)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
// 7.0: the trap is CLOSED. Instance .verify() now returns a STRICT boolean, so
|
|
53
|
-
// `if (ecdsa.verify())` correctly rejects a forgery. (Pre-7.0 it returned the
|
|
54
|
-
// truthy instance with the real result on .verified; that idiom is gone.)
|
|
55
|
-
it('instance .verify() returns a strict boolean and fails closed on a forgery', function () {
|
|
56
|
-
var good = new ECDSA(); good.hashbuf = hash; good.sig = goodSig; good.pubkey = rightPub
|
|
57
|
-
isStrictBool(good.verify()).should.equal(true)
|
|
58
|
-
good.verify().should.equal(true)
|
|
59
|
-
|
|
60
|
-
var forged = new ECDSA(); forged.hashbuf = hash; forged.sig = goodSig; forged.pubkey = wrongPub
|
|
61
|
-
isStrictBool(forged.verify()).should.equal(true)
|
|
62
|
-
forged.verify().should.equal(false) // forged → false, and `if (forged.verify())` does NOT enter
|
|
63
|
-
// The result is still mirrored on .verified as a side effect.
|
|
64
|
-
forged.verified.should.equal(false)
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('SmartVerify (hardened secp256k1 verify)', function () {
|
|
69
|
-
it('returns a STRICT boolean and rejects a forged signature', function () {
|
|
70
|
-
var ok = bsv.SmartVerify.smartVerify(hash, goodSig.toBuffer(), rightPub)
|
|
71
|
-
var bad = bsv.SmartVerify.smartVerify(hash, goodSig.toBuffer(), wrongPub)
|
|
72
|
-
isStrictBool(ok).should.equal(true)
|
|
73
|
-
isStrictBool(bad).should.equal(true)
|
|
74
|
-
ok.should.equal(true)
|
|
75
|
-
bad.should.equal(false)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe('SPV inclusion / header-chain verification', function () {
|
|
80
|
-
it('verifyHeaderChain fails closed on empty / malformed input (throws, not truthy)', function () {
|
|
81
|
-
assert.throws(function () { bsv.SPV.verifyHeaderChain([], {}) })
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('verifyMerkleProof rejects a bad proof without ever returning a truthy non-boolean', function () {
|
|
85
|
-
var fakeRoot = Buffer.alloc(32, 1)
|
|
86
|
-
var fakeTxid = Buffer.alloc(32, 2)
|
|
87
|
-
var res
|
|
88
|
-
try {
|
|
89
|
-
res = bsv.SPV.verifyMerkleProof(fakeTxid, { index: 0, nodes: ['00'.repeat(32)] }, fakeRoot)
|
|
90
|
-
} catch (e) { res = false } // throwing is an acceptable fail-closed outcome
|
|
91
|
-
// Whatever the shape, it must not be a truthy value that reads as "included".
|
|
92
|
-
var included = (res === true) || (res && res.valid === true) || (res && res.verified === true)
|
|
93
|
-
included.should.equal(false)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('SmartContract script verification', function () {
|
|
98
|
-
it('verifyScript reports a boolean ok and fails an unsatisfiable lock', function () {
|
|
99
|
-
var r = bsv.SmartContract.verifyScript(new bsv.Script(), new bsv.Script().add(bsv.Opcode.OP_FALSE))
|
|
100
|
-
isStrictBool(r.ok).should.equal(true)
|
|
101
|
-
r.ok.should.equal(false)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// Keeps docs/THREAT_MODEL.md honest: every `test/…js` the threat model cites as the
|
|
6
|
-
// enforcer of a security property must actually exist. If a security test is renamed
|
|
7
|
-
// or deleted, the threat model can no longer silently point at a ghost — this gate goes
|
|
8
|
-
// red. (Same "docs are tested claims" principle as the type-drift gate.)
|
|
9
|
-
|
|
10
|
-
require('chai').should()
|
|
11
|
-
var fs = require('fs')
|
|
12
|
-
var path = require('path')
|
|
13
|
-
|
|
14
|
-
var ROOT = path.resolve(__dirname, '../..')
|
|
15
|
-
var MODEL = path.join(ROOT, 'docs/THREAT_MODEL.md')
|
|
16
|
-
|
|
17
|
-
describe('security: threat model references real tests', function () {
|
|
18
|
-
var text = fs.readFileSync(MODEL, 'utf8')
|
|
19
|
-
// Every referenced test path, e.g. `test/security/fail_closed_contracts.js`.
|
|
20
|
-
var refs = (text.match(/test\/[A-Za-z0-9_./-]+\.js/g) || [])
|
|
21
|
-
var unique = refs.filter(function (r, i) { return refs.indexOf(r) === i })
|
|
22
|
-
|
|
23
|
-
it('cites a meaningful number of enforcing tests', function () {
|
|
24
|
-
unique.length.should.be.above(6)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('every cited test file exists', function () {
|
|
28
|
-
var missing = unique.filter(function (rel) { return !fs.existsSync(path.join(ROOT, rel)) })
|
|
29
|
-
missing.should.deep.equal([], 'THREAT_MODEL.md cites test files that do not exist:\n ' + missing.join('\n '))
|
|
30
|
-
})
|
|
31
|
-
})
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it, before, after */
|
|
4
|
-
require('chai').should()
|
|
5
|
-
var bsv = require('../..')
|
|
6
|
-
var SC = bsv.SmartContract
|
|
7
|
-
var PrivateKey = bsv.PrivateKey
|
|
8
|
-
var Script = bsv.Script
|
|
9
|
-
var Transaction = bsv.Transaction
|
|
10
|
-
|
|
11
|
-
var Locks = SC.Locks
|
|
12
|
-
var PushTx = SC.PushTx
|
|
13
|
-
var Token = SC.Token
|
|
14
|
-
var help = SC.CovenantHelpers
|
|
15
|
-
var fundAndSpend = help.fundAndSpend
|
|
16
|
-
var p2pkhOutput = help.p2pkhOutput
|
|
17
|
-
var verify = help.verify
|
|
18
|
-
|
|
19
|
-
var SATS = 100000
|
|
20
|
-
|
|
21
|
-
describe('SmartContract covenants (v4.2.0)', function () {
|
|
22
|
-
this.timeout(20000)
|
|
23
|
-
var I = bsv.Script.Interpreter
|
|
24
|
-
var saved
|
|
25
|
-
|
|
26
|
-
before(function () {
|
|
27
|
-
saved = I.getLimits()
|
|
28
|
-
SC.enableGenesis() // OP_PUSH_TX covenants need post-Genesis limits
|
|
29
|
-
})
|
|
30
|
-
after(function () {
|
|
31
|
-
I.setLimits(saved)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
var alice = PrivateKey.fromRandom()
|
|
35
|
-
var bob = PrivateKey.fromRandom()
|
|
36
|
-
var carol = PrivateKey.fromRandom()
|
|
37
|
-
|
|
38
|
-
function spendOf (lock, sats, outputs) {
|
|
39
|
-
return fundAndSpend(lock, sats, { outputs: outputs }).spend
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe('Locks', function () {
|
|
43
|
-
it('hash-lock accepts the right secret and rejects a wrong one', function () {
|
|
44
|
-
var secret = Buffer.from('correct horse battery staple')
|
|
45
|
-
var c = Locks.hashLock(secret)
|
|
46
|
-
var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
47
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
48
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
49
|
-
|
|
50
|
-
spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
51
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS, Buffer.from('wrong')))
|
|
52
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('P2PKH verifies a valid signature', function () {
|
|
56
|
-
var c = Locks.p2pkh(alice)
|
|
57
|
-
var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
|
|
58
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
59
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('CLTV accepts a matured spend and rejects an immature one', function () {
|
|
63
|
-
var lt = 800000
|
|
64
|
-
var c = Locks.timeLockCLTV(alice, lt)
|
|
65
|
-
var spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
66
|
-
spend.nLockTime = lt; spend.inputs[0].sequenceNumber = 0xfffffffe
|
|
67
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
68
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
69
|
-
|
|
70
|
-
spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
71
|
-
spend.nLockTime = lt - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
|
|
72
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
73
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('2-of-3 multisig verifies with two valid signatures', function () {
|
|
77
|
-
var c = Locks.multisig(2, [alice, bob, carol])
|
|
78
|
-
var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
|
|
79
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS, [alice, bob]))
|
|
80
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('HTLC supports claim and rejects an early refund', function () {
|
|
84
|
-
var secret = Buffer.from('lightning-preimage-32bytes------')
|
|
85
|
-
var timeout = 750000
|
|
86
|
-
var c = Locks.htlc({ secret: secret, receiver: bob, sender: alice, timeout: timeout })
|
|
87
|
-
var spend = spendOf(c.lock, SATS, [p2pkhOutput(bob, SATS - 500)])
|
|
88
|
-
spend.inputs[0].setScript(c.unlockClaim(spend, SATS))
|
|
89
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
90
|
-
|
|
91
|
-
spend = spendOf(c.lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
92
|
-
spend.nLockTime = timeout - 1; spend.inputs[0].sequenceNumber = 0xfffffffe
|
|
93
|
-
spend.inputs[0].setScript(c.unlockRefund(spend, SATS))
|
|
94
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe('OP_PUSH_TX', function () {
|
|
99
|
-
it('accepts an authentic preimage and rejects a tampered one', function () {
|
|
100
|
-
var lock = PushTx.authenticator()
|
|
101
|
-
var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
102
|
-
var g = PushTx.grind(spend, 0, lock, SATS)
|
|
103
|
-
spend.inputs[0].setScript(new Script().add(g.preimage))
|
|
104
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
105
|
-
|
|
106
|
-
var bad = Buffer.from(g.preimage); bad[bad.length - 5] ^= 0xff
|
|
107
|
-
spend.inputs[0].setScript(new Script().add(bad))
|
|
108
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('value covenant forces coins to the required outputs', function () {
|
|
112
|
-
var beneficiary = PrivateKey.fromRandom()
|
|
113
|
-
var attacker = PrivateKey.fromRandom()
|
|
114
|
-
var required = [p2pkhOutput(beneficiary, SATS - 500)]
|
|
115
|
-
var lock = PushTx.valueCovenant(PushTx.hashOutputs(required))
|
|
116
|
-
|
|
117
|
-
var spend = spendOf(lock, SATS, required)
|
|
118
|
-
spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
|
|
119
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
120
|
-
|
|
121
|
-
spend = spendOf(lock, SATS, [p2pkhOutput(attacker, SATS - 500)])
|
|
122
|
-
spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
|
|
123
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('grind yields a CANONICAL low-S signature, enforced under SCRIPT_VERIFY_LOW_S', function () {
|
|
127
|
-
var BN = bsv.crypto.BN
|
|
128
|
-
var Hash = bsv.crypto.Hash
|
|
129
|
-
var halfN = BN.fromBuffer(Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))
|
|
130
|
-
var I = bsv.Script.Interpreter
|
|
131
|
-
// The hardened verify flags enforce low-S.
|
|
132
|
-
;(help.flags() & I.SCRIPT_VERIFY_LOW_S).should.not.equal(0)
|
|
133
|
-
|
|
134
|
-
var lock = PushTx.authenticator()
|
|
135
|
-
var spend = spendOf(lock, SATS, [p2pkhOutput(alice, SATS - 500)])
|
|
136
|
-
var preimage = PushTx.grind(spend, 0, lock, SATS).preimage
|
|
137
|
-
// The in-script signature s = (HASH256(preimage)+Gx) mod n is provably low-S.
|
|
138
|
-
var s = BN.fromBuffer(Hash.sha256sha256(preimage)).add(BN.fromBuffer(PushTx.Gx)).mod(PushTx.N)
|
|
139
|
-
s.gt(halfN).should.equal(false)
|
|
140
|
-
// And it verifies with LOW_S enforced (a high-S signature would be rejected here).
|
|
141
|
-
spend.inputs[0].setScript(new Script().add(preimage))
|
|
142
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
describe('PELS (perpetual covenant)', function () {
|
|
147
|
-
var FEE = 500
|
|
148
|
-
it('recreates the same script and chains a second hop', function () {
|
|
149
|
-
var lock = SC.perpetualCovenant(FEE)
|
|
150
|
-
var spend = spendOf(lock, SATS, [new Transaction.Output({ script: lock, satoshis: SATS - FEE })])
|
|
151
|
-
spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
|
|
152
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
153
|
-
|
|
154
|
-
var inSats = SATS - FEE
|
|
155
|
-
spend = spendOf(lock, inSats, [new Transaction.Output({ script: lock, satoshis: inSats - FEE })])
|
|
156
|
-
spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, inSats).preimage))
|
|
157
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: inSats }).ok.should.equal(true)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('rejects escaping to a P2PKH', function () {
|
|
161
|
-
var lock = SC.perpetualCovenant(FEE)
|
|
162
|
-
var spend = spendOf(lock, SATS, [p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE)])
|
|
163
|
-
spend.inputs[0].setScript(new Script().add(PushTx.grind(spend, 0, lock, SATS).preimage))
|
|
164
|
-
verify(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
describe('Token (stateful NFT)', function () {
|
|
169
|
-
var FEE = 500
|
|
170
|
-
|
|
171
|
-
// Owners are keys, not secrets: ownerId = HASH160(pubkey); transfers are ECDSA-signed.
|
|
172
|
-
function transfer (currentLock, inSats, ownerKey, nextHash, outputOverride) {
|
|
173
|
-
var nextLock = Token.ownershipToken(FEE, nextHash)
|
|
174
|
-
var out = outputOverride || new Transaction.Output({ script: nextLock, satoshis: inSats - FEE })
|
|
175
|
-
var spend = spendOf(currentLock, inSats, [out])
|
|
176
|
-
spend.inputs[0].setScript(Token.unlockTransfer(ownerKey, nextHash, spend, inSats, currentLock))
|
|
177
|
-
return { spend: spend, nextLock: nextLock }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
it('transfers ownership across a chain and rejects a non-owner', function () {
|
|
181
|
-
var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
|
|
182
|
-
var bobHash = Token.ownerId(bob)
|
|
183
|
-
var carolHash = Token.ownerId(carol)
|
|
184
|
-
|
|
185
|
-
var r = transfer(aliceToken, SATS, alice, bobHash)
|
|
186
|
-
verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
|
|
187
|
-
|
|
188
|
-
var inSats = SATS - FEE
|
|
189
|
-
var r2 = transfer(r.nextLock, inSats, bob, carolHash)
|
|
190
|
-
verify(r2.spend.inputs[0].script, r.nextLock, { tx: r2.spend, satoshis: inSats }).ok.should.equal(true)
|
|
191
|
-
|
|
192
|
-
var bad = transfer(aliceToken, SATS, carol, bobHash) // carol does not own alice's token
|
|
193
|
-
verify(bad.spend.inputs[0].script, aliceToken, { tx: bad.spend, satoshis: SATS }).ok.should.equal(false)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('rejects breaking the token out to a P2PKH', function () {
|
|
197
|
-
var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
|
|
198
|
-
var r = transfer(aliceToken, SATS, alice, Token.ownerId(bob),
|
|
199
|
-
p2pkhOutput(PrivateKey.fromRandom(), SATS - FEE))
|
|
200
|
-
verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(false)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('defeats front-running: a third party cannot redirect a signed transfer', function () {
|
|
204
|
-
var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
|
|
205
|
-
var bobHash = Token.ownerId(bob)
|
|
206
|
-
var mallory = PrivateKey.fromRandom()
|
|
207
|
-
var malloryHash = Token.ownerId(mallory)
|
|
208
|
-
|
|
209
|
-
// Alice signs a valid transfer to Bob.
|
|
210
|
-
var r = transfer(aliceToken, SATS, alice, bobHash)
|
|
211
|
-
verify(r.spend.inputs[0].script, aliceToken, { tx: r.spend, satoshis: SATS }).ok.should.equal(true)
|
|
212
|
-
|
|
213
|
-
// A mempool watcher lifts Alice's signature + pubkey (chunks 1 and 2 of her
|
|
214
|
-
// unlock script) and tries to redirect the token to Mallory by recreating
|
|
215
|
-
// the token output owned by Mallory and reusing Alice's signature.
|
|
216
|
-
var chunks = r.spend.inputs[0].script.chunks
|
|
217
|
-
var stolenSig = chunks[1].buf
|
|
218
|
-
var stolenPub = chunks[2].buf
|
|
219
|
-
|
|
220
|
-
var malloryLock = Token.ownershipToken(FEE, malloryHash)
|
|
221
|
-
var evil = spendOf(aliceToken, SATS, [new Transaction.Output({ script: malloryLock, satoshis: SATS - FEE })])
|
|
222
|
-
var g = PushTx.grind(evil, 0, aliceToken, SATS) // a valid OP_PUSH_TX preimage for the evil tx
|
|
223
|
-
evil.inputs[0].setScript(new Script()
|
|
224
|
-
.add(Buffer.from(malloryHash))
|
|
225
|
-
.add(stolenSig) // Alice signed the Bob-output spend, not this one
|
|
226
|
-
.add(stolenPub)
|
|
227
|
-
.add(Buffer.from(g.preimage)))
|
|
228
|
-
|
|
229
|
-
// OP_CHECKSIG recomputes the sighash over the Mallory output => Alice's sig fails.
|
|
230
|
-
verify(evil.inputs[0].script, aliceToken, { tx: evil, satoshis: SATS }).ok.should.equal(false)
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('grinds the input sequence so a token can coexist with a pinned nLockTime', function () {
|
|
234
|
-
var aliceToken = Token.ownershipToken(FEE, Token.ownerId(alice))
|
|
235
|
-
var bobHash = Token.ownerId(bob)
|
|
236
|
-
var nextLock = Token.ownershipToken(FEE, bobHash)
|
|
237
|
-
var spend = spendOf(aliceToken, SATS, [new Transaction.Output({ script: nextLock, satoshis: SATS - FEE })])
|
|
238
|
-
spend.nLockTime = 500000 // caller pins nLockTime; grind must not clobber it
|
|
239
|
-
spend.inputs[0].setScript(
|
|
240
|
-
Token.unlockTransfer(alice, bobHash, spend, SATS, aliceToken, { grind: { field: 'sequence' } }))
|
|
241
|
-
spend.nLockTime.should.equal(500000)
|
|
242
|
-
verify(spend.inputs[0].script, aliceToken, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
243
|
-
})
|
|
244
|
-
})
|
|
245
|
-
})
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it, before, after */
|
|
4
|
-
require('chai').should()
|
|
5
|
-
var bsv = require('../..')
|
|
6
|
-
var SC = bsv.SmartContract
|
|
7
|
-
var Script = bsv.Script
|
|
8
|
-
var PrivateKey = bsv.PrivateKey
|
|
9
|
-
var Opcode = bsv.Opcode
|
|
10
|
-
var H = SC.CovenantHelpers
|
|
11
|
-
var verify = H.verify
|
|
12
|
-
|
|
13
|
-
var SATS = 100000
|
|
14
|
-
|
|
15
|
-
describe('SmartContract covenant DSL + debugger (v4.5.0)', function () {
|
|
16
|
-
this.timeout(20000)
|
|
17
|
-
var I = bsv.Script.Interpreter
|
|
18
|
-
var saved
|
|
19
|
-
before(function () {
|
|
20
|
-
saved = I.getLimits()
|
|
21
|
-
SC.enableGenesis()
|
|
22
|
-
})
|
|
23
|
-
after(function () {
|
|
24
|
-
I.setLimits(saved)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
var alice = PrivateKey.fromRandom()
|
|
28
|
-
var attacker = PrivateKey.fromRandom()
|
|
29
|
-
function spendWith (lock, outputs) {
|
|
30
|
-
return H.fundAndSpend(lock, SATS, { outputs: outputs }).spend
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('policy() DSL', function () {
|
|
34
|
-
it('payTo: compiles a covenant that pins the output and rejects redirects', function () {
|
|
35
|
-
var c = SC.policy().payTo(alice.toAddress(), SATS - 500).compile()
|
|
36
|
-
var spend = spendWith(c.lock, c.outputs)
|
|
37
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
38
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
39
|
-
|
|
40
|
-
spend = spendWith(c.lock, [H.p2pkhOutput(attacker, SATS - 500)])
|
|
41
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
42
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('multi-output: pins the exact ordered set and rejects a tweaked amount', function () {
|
|
46
|
-
var c = SC.policy().payTo(alice.toAddress(), 60000).payTo(attacker.toAddress(), 39500).compile()
|
|
47
|
-
var spend = spendWith(c.lock, c.outputs)
|
|
48
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
49
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
50
|
-
|
|
51
|
-
var bad = spendWith(c.lock, [H.p2pkhOutput(alice, 60001), H.p2pkhOutput(attacker, 39499)])
|
|
52
|
-
bad.inputs[0].setScript(c.unlock(bad, SATS))
|
|
53
|
-
verify(bad.inputs[0].script, c.lock, { tx: bad, satoshis: SATS }).ok.should.equal(false)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('payTo AND lockUntil compose; rejects an early locktime', function () {
|
|
57
|
-
var height = 800000
|
|
58
|
-
var c = SC.policy().payTo(alice.toAddress(), SATS - 500).lockUntil(height).compile()
|
|
59
|
-
var spend = spendWith(c.lock, c.outputs)
|
|
60
|
-
spend.inputs[0].setScript(c.unlock(spend, SATS))
|
|
61
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(true)
|
|
62
|
-
|
|
63
|
-
spend = spendWith(c.lock, c.outputs)
|
|
64
|
-
spend.nLockTime = height - 1
|
|
65
|
-
spend.inputs[0].sequenceNumber = 0xfffffffe
|
|
66
|
-
spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, c.lock, SATS).preimage))
|
|
67
|
-
verify(spend.inputs[0].script, c.lock, { tx: spend, satoshis: SATS }).ok.should.equal(false)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('exposes .perpetual()/.token() shortcuts', function () {
|
|
71
|
-
SC.policy.perpetual(500).toBuffer().length.should.be.above(252)
|
|
72
|
-
var h = bsv.crypto.Hash.sha256ripemd160(bsv.PrivateKey.fromRandom().toPublicKey().toBuffer())
|
|
73
|
-
SC.policy.token(500, h).toBuffer().length.should.be.above(252)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('throws on an empty policy', function () {
|
|
77
|
-
(function () { SC.policy().compile() }).should.throw(/empty policy/)
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
describe('trace() debugger', function () {
|
|
82
|
-
it('traces a hash-lock and agrees with the interpreter (true and false)', function () {
|
|
83
|
-
var secret = Buffer.from('hello')
|
|
84
|
-
var lock = new Script().add(Opcode.OP_SHA256).add(bsv.crypto.Hash.sha256(secret)).add(Opcode.OP_EQUAL)
|
|
85
|
-
var r = SC.trace(new Script().add(secret), lock, { satoshis: 1000 })
|
|
86
|
-
r.ok.should.equal(true)
|
|
87
|
-
r.steps[r.steps.length - 1].stack.join().should.equal('01')
|
|
88
|
-
SC.trace(new Script().add(Buffer.from('nope')), lock, { satoshis: 1000 }).ok.should.equal(false)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('traces an OP_PUSH_TX covenant ending in OP_CHECKSIG -> 01', function () {
|
|
92
|
-
var lock = SC.PushTx.authenticator()
|
|
93
|
-
var spend = H.fundAndSpend(lock, SATS, { outputs: [H.p2pkhOutput(alice, SATS - 500)] }).spend
|
|
94
|
-
spend.inputs[0].setScript(new Script().add(SC.PushTx.grind(spend, 0, lock, SATS).preimage))
|
|
95
|
-
var r = SC.trace(spend.inputs[0].script, lock, { tx: spend, satoshis: SATS })
|
|
96
|
-
r.ok.should.equal(true)
|
|
97
|
-
r.steps[r.steps.length - 1].op.should.equal('OP_CHECKSIG')
|
|
98
|
-
SC.Debugger.format(r).should.contain('VALID')
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// Regression for the inverted OP_SPLIT/OP_DROP idiom in CovenantBuilder.extractField.
|
|
6
|
-
// After OP_SPLIT the RIGHT part is on top, so keeping it needs OP_NIP; the code used
|
|
7
|
-
// OP_DROP (which keeps the LEFT), so RIGHT-strategy fields (value, nSequence,
|
|
8
|
-
// hashOutputs, nLocktime, sighashType) extracted the WRONG bytes — a covenant that
|
|
9
|
-
// either fails to constrain the spend or is unspendable. These tests drive the real
|
|
10
|
-
// builder through the interpreter and assert the extracted bytes are correct.
|
|
11
|
-
|
|
12
|
-
require('chai').should()
|
|
13
|
-
var bsv = require('../..')
|
|
14
|
-
var Interpreter = bsv.Script.Interpreter
|
|
15
|
-
var Script = bsv.Script
|
|
16
|
-
var Opcode = bsv.Opcode
|
|
17
|
-
var Transaction = bsv.Transaction
|
|
18
|
-
var CovenantBuilder = require('../../lib/smart_contract/covenant_builder').CovenantBuilder
|
|
19
|
-
|
|
20
|
-
// Synthetic preimage: 120 bytes of filler + a 52-byte BIP-143 right zone whose
|
|
21
|
-
// fields are individually recognizable.
|
|
22
|
-
var FILLER = Buffer.alloc(120, 0x11)
|
|
23
|
-
var VALUE = Buffer.alloc(8, 0xaa)
|
|
24
|
-
var NSEQ = Buffer.alloc(4, 0xbb)
|
|
25
|
-
var HASHOUT = Buffer.alloc(32, 0xcc)
|
|
26
|
-
var NLOCK = Buffer.alloc(4, 0xdd)
|
|
27
|
-
var SIGHASH = Buffer.alloc(4, 0xee)
|
|
28
|
-
var PREIMAGE = Buffer.concat([FILLER, VALUE, NSEQ, HASHOUT, NLOCK, SIGHASH])
|
|
29
|
-
|
|
30
|
-
// Run `<PREIMAGE> <extractField(name) ops> <expected> OP_EQUAL` through the
|
|
31
|
-
// interpreter (fromASM avoids the separately-tracked _asmToHex issue).
|
|
32
|
-
function extractYields (fieldName, expected) {
|
|
33
|
-
var b = new CovenantBuilder()
|
|
34
|
-
b.extractField(fieldName)
|
|
35
|
-
var extractScript = Script.fromASM(b.operations.join(' '))
|
|
36
|
-
var lock = new Script().add(PREIMAGE)
|
|
37
|
-
extractScript.chunks.forEach(function (c) { lock.chunks.push(c) })
|
|
38
|
-
lock.add(expected).add(Opcode.OP_EQUAL)
|
|
39
|
-
var interp = new Interpreter()
|
|
40
|
-
return interp.verify(new Script(), lock, new Transaction(), 0,
|
|
41
|
-
Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES | Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('CovenantBuilder.extractField (OP_SPLIT/OP_NIP regression)', function () {
|
|
45
|
-
it('extracts RIGHT field "value" (offsetFromEnd 52, len 8)', function () {
|
|
46
|
-
extractYields('value', VALUE).should.equal(true)
|
|
47
|
-
})
|
|
48
|
-
it('extracts RIGHT field "hashOutputs" (offsetFromEnd 40, len 32)', function () {
|
|
49
|
-
extractYields('hashOutputs', HASHOUT).should.equal(true)
|
|
50
|
-
})
|
|
51
|
-
it('extracts RIGHT field "nSequence" (offsetFromEnd 44, len 4)', function () {
|
|
52
|
-
extractYields('nSequence', NSEQ).should.equal(true)
|
|
53
|
-
})
|
|
54
|
-
it('extracts RIGHT field "sighashType" (offsetFromEnd 4, len 4)', function () {
|
|
55
|
-
extractYields('sighashType', SIGHASH).should.equal(true)
|
|
56
|
-
})
|
|
57
|
-
it('extracts LEFT field "hashPrevouts" (offset 4, len 32)', function () {
|
|
58
|
-
// First 4 bytes are filler(nVersion); bytes 4..36 are filler too here.
|
|
59
|
-
extractYields('hashPrevouts', PREIMAGE.slice(4, 36)).should.equal(true)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/* global describe, it */
|
|
4
|
-
|
|
5
|
-
// Phase 1 consolidation guard: the two NON-ENFORCING covenant builders
|
|
6
|
-
// (SmartContract.Covenant and SmartContract.Builder) reduce to plain P2PK and
|
|
7
|
-
// bind nothing about the spend. They must fail loud rather than silently hand back
|
|
8
|
-
// a fake covenant — callers are redirected to the enforcing OP_PUSH_TX API
|
|
9
|
-
// (policy() / PushTx / Token). Explicit { allowNonEnforcing: true } opts in.
|
|
10
|
-
|
|
11
|
-
require('chai').should()
|
|
12
|
-
var bsv = require('../..')
|
|
13
|
-
var SC = bsv.SmartContract
|
|
14
|
-
var PrivateKey = bsv.PrivateKey
|
|
15
|
-
|
|
16
|
-
describe('Non-enforcing covenant builders fail loud (Phase 1)', function () {
|
|
17
|
-
var key = PrivateKey.fromRandom()
|
|
18
|
-
|
|
19
|
-
it('SmartContract.Covenant.createFromP2PKH throws by default', function () {
|
|
20
|
-
;(function () {
|
|
21
|
-
new SC.Covenant(key).createFromP2PKH({ satoshis: 1000, script: '00' })
|
|
22
|
-
}).should.throw(/NON-ENFORCING|allowNonEnforcing/)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('SmartContract.Builder.buildLockingScript throws by default but works with allowNonEnforcing', function () {
|
|
26
|
-
;(function () {
|
|
27
|
-
new SC.Builder(key).buildLockingScript({})
|
|
28
|
-
}).should.throw(/NON-ENFORCING|allowNonEnforcing/)
|
|
29
|
-
|
|
30
|
-
var script = new SC.Builder(key, { allowNonEnforcing: true }).buildLockingScript({})
|
|
31
|
-
script.toBuffer().length.should.be.greaterThan(0)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('SmartContract.Builder.createCovenant throws by default', function () {
|
|
35
|
-
;(function () {
|
|
36
|
-
new SC.Builder(key).createCovenant({ satoshis: 1000, script: '00' })
|
|
37
|
-
}).should.throw(/NON-ENFORCING|allowNonEnforcing/)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('the enforcing path (ordinalLock / valueCovenant) still produces scripts', function () {
|
|
41
|
-
SC.ordinalLock(bsv.SmartContract.Token.ownerId(key)).toBuffer().length.should.be.greaterThan(0)
|
|
42
|
-
SC.valueCovenant(Buffer.alloc(32)).toBuffer().length.should.be.greaterThan(0)
|
|
43
|
-
})
|
|
44
|
-
})
|