Source: script/stack.js

  1. /*!
  2. * stack.js - stack object for hsd
  3. * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
  4. * https://github.com/handshake-org/hsd
  5. */
  6. 'use strict';
  7. const assert = require('bsert');
  8. const bio = require('bufio');
  9. const common = require('./common');
  10. const ScriptNum = require('./scriptnum');
  11. /**
  12. * Stack
  13. * Represents the stack of a Script during execution.
  14. * @alias module:script.Stack
  15. * @property {Buffer[]} items - Stack items.
  16. * @property {Number} length - Size of stack.
  17. */
  18. class Stack extends bio.Struct {
  19. /**
  20. * Create a stack.
  21. * @constructor
  22. * @param {Buffer[]?} [items] - Stack items.
  23. */
  24. constructor(items) {
  25. super();
  26. this.items = items || [];
  27. }
  28. /**
  29. * Get length.
  30. * @returns {Number}
  31. */
  32. get length() {
  33. return this.items.length;
  34. }
  35. /**
  36. * Set length.
  37. * @param {Number} value
  38. */
  39. set length(value) {
  40. this.items.length = value;
  41. }
  42. /**
  43. * Instantiate a value-only iterator.
  44. * @returns {IterableIterator<Buffer>}
  45. */
  46. [Symbol.iterator]() {
  47. return this.items[Symbol.iterator]();
  48. }
  49. /**
  50. * Instantiate a value-only iterator.
  51. * @returns {IterableIterator<Buffer>}
  52. */
  53. values() {
  54. return this.items.values();
  55. }
  56. /**
  57. * Instantiate a key and value iterator.
  58. */
  59. entries() {
  60. return this.items.entries();
  61. }
  62. /**
  63. * Inspect the stack.
  64. * @returns {String} Human-readable stack.
  65. */
  66. format() {
  67. return `<Stack: ${this.toString()}>`;
  68. }
  69. /**
  70. * Convert the stack to a string.
  71. * @returns {String} Human-readable stack.
  72. */
  73. toString() {
  74. const out = [];
  75. for (const item of this.items)
  76. out.push(item.toString('hex'));
  77. return out.join(' ');
  78. }
  79. /**
  80. * Format the stack as bitcoind asm.
  81. * @param {Boolean?} decode - Attempt to decode hash types.
  82. * @returns {String} Human-readable script.
  83. */
  84. toASM(decode) {
  85. const out = [];
  86. for (const item of this.items)
  87. out.push(common.toASM(item, decode));
  88. return out.join(' ');
  89. }
  90. /**
  91. * Clone the stack.
  92. * @param {this} stack
  93. * @returns {this} Cloned stack.
  94. */
  95. inject(stack) {
  96. this.items = stack.items.slice();
  97. return this;
  98. }
  99. /**
  100. * Clear the stack.
  101. * @returns {Stack}
  102. */
  103. clear() {
  104. this.items.length = 0;
  105. return this;
  106. }
  107. /**
  108. * Get a stack item by index.
  109. * @param {Number} index
  110. * @returns {Buffer|null}
  111. */
  112. get(index) {
  113. if (index < 0)
  114. index += this.items.length;
  115. if (index < 0 || index >= this.items.length)
  116. return null;
  117. return this.items[index];
  118. }
  119. /**
  120. * Pop a stack item.
  121. * @see Array#pop
  122. * @returns {Buffer|null}
  123. */
  124. pop() {
  125. const item = this.items.pop();
  126. return item || null;
  127. }
  128. /**
  129. * Shift a stack item.
  130. * @see Array#shift
  131. * @returns {Buffer|null}
  132. */
  133. shift() {
  134. const item = this.items.shift();
  135. return item || null;
  136. }
  137. /**
  138. * Remove an item.
  139. * @param {Number} index
  140. * @returns {Buffer}
  141. */
  142. remove(index) {
  143. if (index < 0)
  144. index += this.items.length;
  145. if (index < 0 || index >= this.items.length)
  146. return null;
  147. const items = this.items.splice(index, 1);
  148. if (items.length === 0)
  149. return null;
  150. return items[0];
  151. }
  152. /**
  153. * Set stack item at index.
  154. * @param {Number} index
  155. * @param {Buffer} item
  156. * @returns {this}
  157. */
  158. set(index, item) {
  159. if (index < 0)
  160. index += this.items.length;
  161. assert(Buffer.isBuffer(item));
  162. assert(index >= 0 && index <= this.items.length);
  163. this.items[index] = item;
  164. return this;
  165. }
  166. /**
  167. * Push item onto stack.
  168. * @see Array#push
  169. * @param {Buffer} item
  170. * @returns {this}
  171. */
  172. push(item) {
  173. assert(Buffer.isBuffer(item));
  174. this.items.push(item);
  175. return this;
  176. }
  177. /**
  178. * Unshift item from stack.
  179. * @see Array#unshift
  180. * @param {Buffer} item
  181. * @returns {this}
  182. */
  183. unshift(item) {
  184. assert(Buffer.isBuffer(item));
  185. this.items.unshift(item);
  186. return this;
  187. }
  188. /**
  189. * Insert an item.
  190. * @param {Number} index
  191. * @param {Buffer} item
  192. * @returns {this}
  193. */
  194. insert(index, item) {
  195. if (index < 0)
  196. index += this.items.length;
  197. assert(Buffer.isBuffer(item));
  198. assert(index >= 0 && index <= this.items.length);
  199. this.items.splice(index, 0, item);
  200. return this;
  201. }
  202. /**
  203. * Erase stack items.
  204. * @param {Number} start
  205. * @param {Number} end
  206. * @returns {Buffer[]}
  207. */
  208. erase(start, end) {
  209. if (start < 0)
  210. start = this.items.length + start;
  211. if (end < 0)
  212. end = this.items.length + end;
  213. return this.items.splice(start, end - start);
  214. }
  215. /**
  216. * Swap stack values.
  217. * @param {Number} i1 - Index 1.
  218. * @param {Number} i2 - Index 2.
  219. */
  220. swap(i1, i2) {
  221. if (i1 < 0)
  222. i1 = this.items.length + i1;
  223. if (i2 < 0)
  224. i2 = this.items.length + i2;
  225. const v1 = this.items[i1];
  226. const v2 = this.items[i2];
  227. this.items[i1] = v2;
  228. this.items[i2] = v1;
  229. }
  230. /*
  231. * Data
  232. */
  233. getData(index) {
  234. return this.get(index);
  235. }
  236. popData() {
  237. return this.pop();
  238. }
  239. shiftData() {
  240. return this.shift();
  241. }
  242. removeData(index) {
  243. return this.remove(index);
  244. }
  245. setData(index, data) {
  246. return this.set(index, data);
  247. }
  248. pushData(data) {
  249. return this.push(data);
  250. }
  251. unshiftData(data) {
  252. return this.unshift(data);
  253. }
  254. insertData(index, data) {
  255. return this.insert(index, data);
  256. }
  257. /*
  258. * Length
  259. */
  260. getLength(index) {
  261. const item = this.get(index);
  262. return item ? item.length : -1;
  263. }
  264. /*
  265. * String
  266. */
  267. getString(index, enc) {
  268. const item = this.get(index);
  269. return item ? Stack.toString(item, enc) : null;
  270. }
  271. popString(enc) {
  272. const item = this.pop();
  273. return item ? Stack.toString(item, enc) : null;
  274. }
  275. shiftString(enc) {
  276. const item = this.shift();
  277. return item ? Stack.toString(item, enc) : null;
  278. }
  279. removeString(index, enc) {
  280. const item = this.remove(index);
  281. return item ? Stack.toString(item, enc) : null;
  282. }
  283. setString(index, str, enc) {
  284. return this.set(index, Stack.fromString(str, enc));
  285. }
  286. pushString(str, enc) {
  287. return this.push(Stack.fromString(str, enc));
  288. }
  289. unshiftString(str, enc) {
  290. return this.unshift(Stack.fromString(str, enc));
  291. }
  292. insertString(index, str, enc) {
  293. return this.insert(index, Stack.fromString(str, enc));
  294. }
  295. /*
  296. * Num
  297. */
  298. getNum(index, minimal, limit) {
  299. const item = this.get(index);
  300. return item ? Stack.toNum(item, minimal, limit) : null;
  301. }
  302. popNum(minimal, limit) {
  303. const item = this.pop();
  304. return item ? Stack.toNum(item, minimal, limit) : null;
  305. }
  306. shiftNum(minimal, limit) {
  307. const item = this.shift();
  308. return item ? Stack.toNum(item, minimal, limit) : null;
  309. }
  310. removeNum(index, minimal, limit) {
  311. const item = this.remove(index);
  312. return item ? Stack.toNum(item, minimal, limit) : null;
  313. }
  314. setNum(index, num) {
  315. return this.set(index, Stack.fromNum(num));
  316. }
  317. pushNum(num) {
  318. return this.push(Stack.fromNum(num));
  319. }
  320. unshiftNum(num) {
  321. return this.unshift(Stack.fromNum(num));
  322. }
  323. insertNum(index, num) {
  324. return this.insert(index, Stack.fromNum(num));
  325. }
  326. /*
  327. * Int
  328. */
  329. getInt(index, minimal, limit) {
  330. const item = this.get(index);
  331. return item ? Stack.toInt(item, minimal, limit) : -1;
  332. }
  333. popInt(minimal, limit) {
  334. const item = this.pop();
  335. return item ? Stack.toInt(item, minimal, limit) : -1;
  336. }
  337. shiftInt(minimal, limit) {
  338. const item = this.shift();
  339. return item ? Stack.toInt(item, minimal, limit) : -1;
  340. }
  341. removeInt(index, minimal, limit) {
  342. const item = this.remove(index);
  343. return item ? Stack.toInt(item, minimal, limit) : -1;
  344. }
  345. setInt(index, num) {
  346. return this.set(index, Stack.fromInt(num));
  347. }
  348. pushInt(num) {
  349. return this.push(Stack.fromInt(num));
  350. }
  351. unshiftInt(num) {
  352. return this.unshift(Stack.fromInt(num));
  353. }
  354. insertInt(index, num) {
  355. return this.insert(index, Stack.fromInt(num));
  356. }
  357. /*
  358. * Bool
  359. */
  360. getBool(index) {
  361. const item = this.get(index);
  362. return item ? Stack.toBool(item) : false;
  363. }
  364. popBool() {
  365. const item = this.pop();
  366. return item ? Stack.toBool(item) : false;
  367. }
  368. shiftBool() {
  369. const item = this.shift();
  370. return item ? Stack.toBool(item) : false;
  371. }
  372. removeBool(index) {
  373. const item = this.remove(index);
  374. return item ? Stack.toBool(item) : false;
  375. }
  376. setBool(index, value) {
  377. return this.set(index, Stack.fromBool(value));
  378. }
  379. pushBool(value) {
  380. return this.push(Stack.fromBool(value));
  381. }
  382. unshiftBool(value) {
  383. return this.unshift(Stack.fromBool(value));
  384. }
  385. insertBool(index, value) {
  386. return this.insert(index, Stack.fromBool(value));
  387. }
  388. /**
  389. * Test an object to see if it is a Stack.
  390. * @param {Object} obj
  391. * @returns {Boolean}
  392. */
  393. static isStack(obj) {
  394. return obj instanceof Stack;
  395. }
  396. /*
  397. * Encoding
  398. */
  399. static toString(item, enc) {
  400. assert(Buffer.isBuffer(item));
  401. return item.toString(enc || 'utf8');
  402. }
  403. static fromString(str, enc) {
  404. assert(typeof str === 'string');
  405. return Buffer.from(str, enc || 'utf8');
  406. }
  407. static toNum(item, minimal, limit) {
  408. return ScriptNum.decode(item, minimal, limit);
  409. }
  410. static fromNum(num) {
  411. assert(ScriptNum.isScriptNum(num));
  412. return num.encode();
  413. }
  414. static toInt(item, minimal, limit) {
  415. const num = Stack.toNum(item, minimal, limit);
  416. return num.getInt();
  417. }
  418. static fromInt(int) {
  419. assert(typeof int === 'number');
  420. if (int >= -1 && int <= 16)
  421. return common.small[int + 1];
  422. const num = ScriptNum.fromNumber(int);
  423. return Stack.fromNum(num);
  424. }
  425. static toBool(item) {
  426. assert(Buffer.isBuffer(item));
  427. for (let i = 0; i < item.length; i++) {
  428. if (item[i] !== 0) {
  429. // Cannot be negative zero
  430. if (i === item.length - 1 && item[i] === 0x80)
  431. return false;
  432. return true;
  433. }
  434. }
  435. return false;
  436. }
  437. static fromBool(value) {
  438. assert(typeof value === 'boolean');
  439. return Stack.fromInt(value ? 1 : 0);
  440. }
  441. }
  442. /*
  443. * Expose
  444. */
  445. module.exports = Stack;