diff --git a/.vscode/launch.json b/.vscode/launch.json index 40d89a15..329a543c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,13 +10,16 @@ "name": "Launch Program", "runtimeArgs": [ "-r", - "ts-node/register" + "ts-node/register/transpile-only" ], "args": [ "${workspaceFolder}/src/index.ts" ], "outputCapture": "std", - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "env": { + "TS_NODE_IGNORE": "none" + } } ] } diff --git a/package-lock.json b/package-lock.json index 8bfab024..587e4ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,12 +68,33 @@ } } }, + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "requires": { + "@types/node": "*" + } + }, + "@types/elliptic": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.12.tgz", + "integrity": "sha512-gP1KsqoouLJGH6IJa28x7PXb3cRqh83X8HCLezd2dF+XcAIMKYv53KV+9Zn6QA561E120uOqZBQ+Jy/cl+fviw==", + "requires": { + "@types/bn.js": "*" + } + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/js-sha512": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@types/js-sha512/-/js-sha512-0.7.0.tgz", + "integrity": "sha512-gPY2v4o+xBinnBDWREx8PzNM1IlHQ0VrP31sxVenRLX56Ssh0IcaQDGzSQBRZxscYYvirDDDsCDNGRcIL7K7Dw==" + }, "@types/json-schema": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", @@ -85,6 +106,30 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.4.tgz", "integrity": "sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==" }, + "@types/randombytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", + "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", + "requires": { + "@types/node": "*" + } + }, + "@types/ripemd160": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", + "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", + "requires": { + "@types/node": "*" + } + }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.20.0.tgz", @@ -210,11 +255,29 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base-x": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-1.1.0.tgz", + "integrity": "sha1-QtPXF0dPnqAiB/bRqh9CaRPut6w=" + }, + "base58check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base58check/-/base58check-2.0.0.tgz", + "integrity": "sha1-gEZlLRS8h/BjvRa+lKORNNO2EXM=", + "requires": { + "bs58": "^3.0.0" + } + }, "big-integer": { "version": "1.6.48", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -225,11 +288,33 @@ "concat-map": "0.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "bs58": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-3.1.0.tgz", + "integrity": "sha1-1MJjiL9IBMrHFBQbGUWqR+XrJI4=", + "requires": { + "base-x": "^1.1.0" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "c32check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/c32check/-/c32check-1.0.1.tgz", + "integrity": "sha512-0e1+39uLNLyRm9sNoPUCg38LOELUqiBDZXD9kpUUAFNBq21gY3VaNImp1rfznQOsqK27ArvQ2JaZmfokZznqHg==", + "requires": { + "base58check": "^2.0.0", + "ripemd160": "^2.0.1" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -339,6 +424,20 @@ "esutils": "^2.0.2" } }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -630,6 +729,34 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -674,8 +801,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "7.0.4", @@ -737,6 +863,11 @@ "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", "dev": true }, + "js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -792,6 +923,16 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -938,6 +1079,14 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, "regexpp": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", @@ -978,6 +1127,15 @@ "glob": "^7.1.3" } }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -996,6 +1154,11 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1008,6 +1171,15 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -1073,6 +1245,25 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "stacks-transactions-js": { + "version": "github:blockstack/stacks-transactions-js#f9e93e42327d3e4e8f7e24412408a8f25db27e7e", + "from": "github:blockstack/stacks-transactions-js#f9e93e4", + "requires": { + "@types/bn.js": "^4.11.6", + "@types/elliptic": "^6.4.12", + "@types/js-sha512": "^0.7.0", + "@types/randombytes": "^2.0.0", + "@types/ripemd160": "^2.0.0", + "@types/sha.js": "^2.4.0", + "bn.js": "^4.11.8", + "c32check": "^1.0.1", + "elliptic": "^6.5.2", + "js-sha512": "^0.8.0", + "randombytes": "^2.1.0", + "ripemd160": "^2.0.2", + "sha.js": "^2.4.11" + } + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", diff --git a/package.json b/package.json index 9154aaf8..cff585d4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/node": "^13.7.4", "big-integer": "^1.6.48", "smart-buffer": "^4.1.0", + "stacks-transactions-js": "github:blockstack/stacks-transactions-js#f9e93e4", "ts-node": "^8.6.2", "typescript": "^3.7.5" }, diff --git a/src/p2p/tx.ts b/src/p2p/tx.ts index 8fc6f578..3f5a47ba 100644 --- a/src/p2p/tx.ts +++ b/src/p2p/tx.ts @@ -1,6 +1,8 @@ import { BufferReader } from '../binary-reader'; import { getEnumDescription } from '../helpers'; import { StacksMessageParsingError, NotImplementedError } from '../errors'; +import * as clarityUtil from '../../node_modules/stacks-transactions-js/src/clarity/clarityTypes'; +import * as stacksTxUtil from '../../node_modules/stacks-transactions-js/src/utils'; enum SigHashMode { /** SingleSigHashMode */ @@ -162,19 +164,14 @@ interface TransactionPostConditionFungible { amount: bigint; // u64 } -// TODO: incomplete interface TransactionPostConditionNonfungible { assetInfoId: AssetInfoTypeID.NonfungibleAsset; // u8 + principal: PostConditionPrincipal; asset: AssetInfo; - assetValue: ClarityValue; + assetValue: clarityUtil.ClarityValue; conditionCode: NonfungibleConditionCode; // u8 } -// TODO: placeholder, needs clarity-js / stacks-transactions-js -interface ClarityValue { - value: Buffer; // wrong -} - type TransactionPostCondition = | TransactionPostConditionStx | TransactionPostConditionFungible @@ -200,9 +197,12 @@ interface TransactionPayloadCoinbase { payload: Buffer; // 32 bytes } -// TODO: incomplete interface TransactionPayloadContractCall { typeId: TransactionPayloadTypeID.ContractCall; + address: StacksAddress; + contractName: string; + functionName: string; + functionArgs: clarityUtil.ClarityValue[]; } interface TransactionPayloadSmartContract { @@ -322,11 +322,39 @@ function readTransactionPayload(reader: BufferReader): TransactionPayload { codeBody: readString(reader), }; return payload; + } else if (txPayloadType === TransactionPayloadTypeID.ContractCall) { + const payload: TransactionPayloadContractCall = { + typeId: txPayloadType, + address: readStacksAddress(reader), + contractName: readContractName(reader), + functionName: readClarityName(reader), + functionArgs: readClarityValueArray(reader), + }; + return payload; } else { throw new NotImplementedError(`tx payload type: ${getEnumDescription(TransactionPayloadTypeID, txPayloadType)}`); } } +function readClarityValue(reader: BufferReader): clarityUtil.ClarityValue { + const remainingBuffer = reader.internalBuffer.slice(reader.readOffset); + const bufferReader = new stacksTxUtil.BufferReader(remainingBuffer); + const clarityVal = clarityUtil.ClarityValue.deserialize(bufferReader); + return clarityVal; +} + +function readClarityValueArray(reader: BufferReader): clarityUtil.ClarityValue[] { + const valueCount = reader.readUInt32BE(); + const values = new Array(valueCount); + const remainingBuffer = reader.internalBuffer.slice(reader.readOffset); + const bufferReader = new stacksTxUtil.BufferReader(remainingBuffer); + for (let i = 0; i < valueCount; i++) { + const clarityVal = clarityUtil.ClarityValue.deserialize(bufferReader); + values[i] = clarityVal; + } + return values; +} + function readString(reader: BufferReader): string { const length = reader.readUInt32BE(); const str = reader.readString(length, 'ascii'); @@ -339,6 +367,12 @@ function readContractName(reader: BufferReader): string { return name; } +function readClarityName(reader: BufferReader): string { + const length = reader.readUInt8(); + const name = reader.readString(length, 'ascii'); + return name; +} + function readStacksAddress(reader: BufferReader): StacksAddress { const address: StacksAddress = { version: reader.readUInt8(), @@ -347,6 +381,15 @@ function readStacksAddress(reader: BufferReader): StacksAddress { return address; } +function readAssetInfo(reader: BufferReader): AssetInfo { + const assetInfo: AssetInfo = { + contractAddress: readStacksAddress(reader), + contractName: readContractName(reader), + assetName: readClarityName(reader), + }; + return assetInfo; +} + function readTransactionPostConditions(reader: BufferReader): TransactionPostCondition[] { const conditionCount = reader.readUInt32BE(); const conditions = new Array(conditionCount); @@ -354,16 +397,39 @@ function readTransactionPostConditions(reader: BufferReader): TransactionPostCon const typeId = reader.readUInt8Enum(AssetInfoTypeID, n => { throw new StacksMessageParsingError(`unexpected tx asset info type: ${n}`); }); + const principal = readTransactionPostConditionPrincipal(reader); if (typeId === AssetInfoTypeID.STX) { - const principal = readTransactionPostConditionPrincipal(reader); - const conditionCode: FungibleConditionCode = reader.readUInt8(); const condition: TransactionPostConditionStx = { assetInfoId: typeId, principal: principal, - conditionCode: conditionCode, + conditionCode: reader.readUInt8Enum(FungibleConditionCode, n => { + throw new StacksMessageParsingError(`unexpected condition code: ${n}`); + }), amount: reader.readBigInt64BE(), }; conditions[i] = condition; + } else if (typeId === AssetInfoTypeID.FungibleAsset) { + const condition: TransactionPostConditionFungible = { + assetInfoId: typeId, + principal: principal, + asset: readAssetInfo(reader), + conditionCode: reader.readUInt8Enum(FungibleConditionCode, n => { + throw new StacksMessageParsingError(`unexpected condition code: ${n}`); + }), + amount: reader.readBigUInt64BE(), + }; + conditions[i] = condition; + } else if (typeId === AssetInfoTypeID.NonfungibleAsset) { + const condition: TransactionPostConditionNonfungible = { + assetInfoId: typeId, + principal: principal, + asset: readAssetInfo(reader), + assetValue: readClarityValue(reader), + conditionCode: reader.readUInt8Enum(NonfungibleConditionCode, n => { + throw new StacksMessageParsingError(`unexpected nonfungible condition code: ${n}`); + }), + }; + conditions[i] = condition; } else { throw new NotImplementedError(`tx asset info type ${getEnumDescription(AssetInfoTypeID, typeId)}`); } diff --git a/tsconfig.json b/tsconfig.json index 9504b1ce..0878e6b1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,10 +8,11 @@ "outDir": "lib", "sourceMap": true, "sourceRoot": ".", - "esModuleInterop": true, + "esModuleInterop": false, "allowSyntheticDefaultImports": false, }, "include": [ - "src/**/*" + "src/**/*", + "node_modules/stacks-transactions-js/src/**/*" ] }