├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── justfile ├── lerna.json ├── package.json ├── packages ├── bitcoin │ ├── README.md │ ├── __tests__ │ │ ├── bitcoin │ │ │ ├── amount.spec.ts │ │ │ └── value.spec.ts │ │ └── tsconfig.json │ ├── bitcoin │ ├── lib │ │ ├── Amount.ts │ │ ├── Base58.ts │ │ ├── Base58Check.ts │ │ ├── BitcoinError.ts │ │ ├── BitcoinErrorCode.ts │ │ ├── Block.ts │ │ ├── HashByteOrder.ts │ │ ├── HashValue.ts │ │ ├── ICloneable.ts │ │ ├── LexicographicalSorters.ts │ │ ├── LockTime.ts │ │ ├── OpCodes.ts │ │ ├── OutPoint.ts │ │ ├── Script.ts │ │ ├── ScriptCmd.ts │ │ ├── Sequence.ts │ │ ├── SigHashType.ts │ │ ├── SizeResult.ts │ │ ├── Sorter.ts │ │ ├── Stack.ts │ │ ├── TimeLockMode.ts │ │ ├── Tx.ts │ │ ├── TxBuilder.ts │ │ ├── TxIn.ts │ │ ├── TxOut.ts │ │ ├── Value.ts │ │ ├── Wif.ts │ │ ├── Witness.ts │ │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── bitcoind │ ├── .eslintrc │ ├── README.md │ ├── __tests__ │ │ └── policies │ │ │ ├── constant-backoff.spec.ts │ │ │ ├── exponential-backoff.spec.ts │ │ │ ├── retry-policy.spec.ts │ │ │ └── wait.spec.ts │ ├── lib │ │ ├── bitcoind-client.ts │ │ ├── bitcoind-options.ts │ │ ├── index.ts │ │ ├── jsonrpc-error.ts │ │ ├── jsonrpc-request.ts │ │ ├── policies │ │ │ ├── backoff-strategy.ts │ │ │ ├── constant-backoff.ts │ │ │ ├── exponential-backoff.ts │ │ │ ├── policy.ts │ │ │ ├── retry-policy.ts │ │ │ └── wait.ts │ │ ├── types │ │ │ ├── BlockHeader.ts │ │ │ ├── block-chain-info.ts │ │ │ ├── blocksummary.ts │ │ │ └── transaction.ts │ │ └── wait.ts │ ├── package-lock.json │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── bufio │ ├── .eslintrc │ ├── README.md │ ├── __tests__ │ │ ├── BufferReader.spec.ts │ │ ├── BufferWriter.spec.ts │ │ ├── Hex.spec.ts │ │ ├── StreamReader.spec.ts │ │ ├── bigFromBufBE.spec.ts │ │ ├── bigFromBufLE.spec.ts │ │ ├── bigToBufBE.spec.ts │ │ ├── bigToBufLE.spec.ts │ │ ├── encodeVarInt.spec.ts │ │ └── varIntBytes.spec.ts │ ├── bufio │ ├── lib │ │ ├── BufferReader.ts │ │ ├── BufferWriter.ts │ │ ├── Hex.ts │ │ ├── StreamReader.ts │ │ ├── bigFromBufBE.ts │ │ ├── bigFromBufLE.ts │ │ ├── bigToBufBE.ts │ │ ├── bigToBufLE.ts │ │ ├── bufToStream.ts │ │ ├── encodeVarInt.ts │ │ ├── index.ts │ │ └── varIntBytes.ts │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── chainmon │ ├── README.md │ ├── __tests__ │ │ ├── chainmon.spec.ts │ │ └── tsconfig.json │ ├── lib │ │ ├── BlockDiffResult.ts │ │ ├── BlockDiffer.ts │ │ ├── BlockWatcher.ts │ │ ├── TxWatcher.ts │ │ └── index.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── checksum │ ├── .eslintrc │ ├── README.md │ ├── __tests__ │ │ └── crc32c.spec.ts │ ├── checksum │ ├── lib │ │ ├── crc32c.ts │ │ └── index.ts │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── common │ ├── __tests__ │ │ └── bitfield.spec.ts │ ├── lib │ │ ├── Base32.ts │ │ ├── BigIntUtils.ts │ │ ├── BitField.ts │ │ ├── ChannelId.ts │ │ ├── ShortChannelId.ts │ │ ├── ShortChannelIdUtils.ts │ │ └── index.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── core │ ├── README.md │ ├── __tests__ │ │ ├── core.spec.ts │ │ ├── dlc │ │ │ ├── CETCalculator.spec.ts │ │ │ ├── CoinSelect.spec.ts │ │ │ ├── PolynomialPayoutCurve.spec.ts │ │ │ └── finance │ │ │ │ ├── Builder.spec.ts │ │ │ │ ├── CoveredCall.spec.ts │ │ │ │ ├── CsoInfo.spec.ts │ │ │ │ ├── LongCall.spec.ts │ │ │ │ ├── LongPut.spec.ts │ │ │ │ ├── OptionInfo.spec.ts │ │ │ │ └── ShortPut.spec.ts │ │ ├── tsconfig.json │ │ └── utils │ │ │ └── precision.spec.ts │ ├── lib │ │ ├── AsyncProcessingQueue.ts │ │ ├── Base32.ts │ │ ├── BigIntUtils.ts │ │ ├── BitField.ts │ │ ├── ChannelId.ts │ │ ├── LinkedList.ts │ │ ├── LinkedListNode.ts │ │ ├── Queue.ts │ │ ├── ShortChannelId.ts │ │ ├── ShortChannelIdUtils.ts │ │ ├── dlc │ │ │ ├── CETCalculator.ts │ │ │ ├── CoinSelect.ts │ │ │ ├── HyperbolaPayoutCurve.ts │ │ │ ├── PayoutCurve.ts │ │ │ ├── PolynomialPayoutCurve.ts │ │ │ ├── TxBuilder.ts │ │ │ ├── TxFinalizer.ts │ │ │ └── finance │ │ │ │ ├── Builder.ts │ │ │ │ ├── CoveredCall.ts │ │ │ │ ├── CsoInfo.ts │ │ │ │ ├── LinearPayout.ts │ │ │ │ ├── LongCall.ts │ │ │ │ ├── LongPut.ts │ │ │ │ ├── OptionInfo.ts │ │ │ │ └── ShortPut.ts │ │ ├── index.ts │ │ ├── lightning │ │ │ ├── ChannelId.ts │ │ │ ├── ChannelKeys.ts │ │ │ ├── CommitmentNumber.ts │ │ │ ├── CommitmentSecret.ts │ │ │ ├── CommitmentSecretStore.ts │ │ │ ├── Htlc.ts │ │ │ ├── HtlcDirection.ts │ │ │ ├── ScriptFactory.ts │ │ │ └── TxFactory.ts │ │ └── utils │ │ │ ├── BigIntUtils.ts │ │ │ └── Precision.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── crypto │ ├── .eslintrc │ ├── README.md │ ├── __tests__ │ │ ├── chacha.spec.ts │ │ ├── chachapoly.spec.ts │ │ ├── hkdf.spec.ts │ │ ├── key.spec.ts │ │ ├── secp256k1.spec.ts │ │ └── xor.spec.ts │ ├── crypto │ ├── lib │ │ ├── aes-key.ts │ │ ├── aes.ts │ │ ├── chacha.ts │ │ ├── chachapoly.ts │ │ ├── hash.ts │ │ ├── hkdf.ts │ │ ├── hmac.ts │ │ ├── index.ts │ │ ├── key.ts │ │ ├── secp256k1.ts │ │ └── xor.ts │ ├── package-lock.json │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── leveldb │ ├── README.md │ ├── __tests__ │ │ ├── leveldb-dlc-store.spec.ts │ │ ├── leveldb-gossip-store.spec.ts │ │ ├── leveldb-info-store.spec.ts │ │ ├── leveldb-irc-store.spec.ts │ │ ├── leveldb-oracle-store.spec.ts │ │ ├── leveldb-order-store.spec.ts │ │ ├── leveldb-wallet-store.spec.ts │ │ ├── leveldb.ts │ │ └── tsconfig.json │ ├── lib │ │ ├── index.ts │ │ ├── leveldb-base.ts │ │ ├── leveldb-dlc-store.ts │ │ ├── leveldb-gossip-store.ts │ │ ├── leveldb-info-store.ts │ │ ├── leveldb-irc-store.ts │ │ ├── leveldb-oracle-store.ts │ │ ├── leveldb-order-store.ts │ │ └── leveldb-wallet-store.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── logger │ ├── README.md │ ├── __tests__ │ │ ├── logger │ │ │ └── logger.spec.ts │ │ └── tsconfig.json │ ├── lib │ │ ├── index.ts │ │ ├── log-level.ts │ │ ├── logger.ts │ │ ├── transport.ts │ │ ├── transports │ │ │ ├── console-transport.ts │ │ │ └── file-transport.ts │ │ └── util.ts │ ├── logger │ ├── package.json │ └── tsconfig.json ├── messaging │ ├── README.md │ ├── __tests__ │ │ ├── _test-utils.ts │ │ ├── chain │ │ │ └── ChainManager.spec.ts │ │ ├── dlc_message_test.json │ │ ├── irc │ │ │ └── IrcMessage.spec.ts │ │ ├── messages │ │ │ ├── AddressCache.spec.ts │ │ │ ├── BatchFundingGroup.spec.ts │ │ │ ├── CetAdaptorSignaturesV0.spec.ts │ │ │ ├── ContractDescriptorV0.spec.ts │ │ │ ├── ContractInfoV0.spec.ts │ │ │ ├── DigitDecompositionEventDescriptorV0.spec.ts │ │ │ ├── DlcAcceptV0.spec.ts │ │ │ ├── DlcCancelV0.spec.ts │ │ │ ├── DlcCloseV0.spec.ts │ │ │ ├── DlcOfferV0.spec.ts │ │ │ ├── DlcSignV0.spec.ts │ │ │ ├── DlcTransactionsV0.spec.ts │ │ │ ├── EnumEventDescriptorV0.spec.ts │ │ │ ├── EventDescriptorV0.spec.ts │ │ │ ├── FundingInput.spec.ts │ │ │ ├── NegotiationFields.spec.ts │ │ │ ├── NodeAnnouncementMessage.spec.ts │ │ │ ├── OracleAnnouncementV0.spec.ts │ │ │ ├── OracleAttestationV0.spec.ts │ │ │ ├── OracleEventContainerV0.spec.ts │ │ │ ├── OracleEventV0.spec.ts │ │ │ ├── OracleIdentifierV0.spec.ts │ │ │ ├── OracleInfoV0.spec.ts │ │ │ ├── OrderAccept.spec.ts │ │ │ ├── OrderNegotiationFields.spec.ts │ │ │ ├── OrderOffer.spec.ts │ │ │ ├── OrderPositionInfo.spec.ts │ │ │ ├── PayoutCurvePiece.spec.ts │ │ │ ├── PayoutFunctionV0.spec.ts │ │ │ └── RoundingIntervalV0.spec.ts │ │ └── tsconfig.json │ ├── lib │ │ ├── MessageType.ts │ │ ├── chain │ │ │ ├── ChainManager.ts │ │ │ ├── ChainMemoryStore.ts │ │ │ ├── DlcStore.ts │ │ │ └── IChainFilterChainClient.ts │ │ ├── domain │ │ │ └── Address.ts │ │ ├── index.ts │ │ ├── irc │ │ │ └── IrcMessage.ts │ │ ├── messages │ │ │ ├── AddressCache.ts │ │ │ ├── BatchFundingGroup.ts │ │ │ ├── CetAdaptorSignaturesV0.ts │ │ │ ├── ContractDescriptor.ts │ │ │ ├── ContractInfo.ts │ │ │ ├── DlcAccept.ts │ │ │ ├── DlcCancel.ts │ │ │ ├── DlcClose.ts │ │ │ ├── DlcCloseMetadata.ts │ │ │ ├── DlcIds.ts │ │ │ ├── DlcInfo.ts │ │ │ ├── DlcMessage.ts │ │ │ ├── DlcOffer.ts │ │ │ ├── DlcSign.ts │ │ │ ├── DlcTransactions.ts │ │ │ ├── EventDescriptor.ts │ │ │ ├── FundingInput.ts │ │ │ ├── FundingSignaturesV0.ts │ │ │ ├── IWireMessage.ts │ │ │ ├── NegotiationFields.ts │ │ │ ├── NodeAnnouncementMessage.ts │ │ │ ├── OracleAnnouncementV0.ts │ │ │ ├── OracleAttestationV0.ts │ │ │ ├── OracleEventContainerV0.ts │ │ │ ├── OracleEventV0.ts │ │ │ ├── OracleIdentifierV0.ts │ │ │ ├── OracleInfoV0.ts │ │ │ ├── OrderAccept.ts │ │ │ ├── OrderIrcInfo.ts │ │ │ ├── OrderMetadata.ts │ │ │ ├── OrderNegotiationFields.ts │ │ │ ├── OrderOffer.ts │ │ │ ├── OrderPositionInfo.ts │ │ │ ├── PayoutCurvePiece.ts │ │ │ ├── PayoutFunction.ts │ │ │ ├── RoundingIntervalsV0.ts │ │ │ ├── ScriptWitnessV0.ts │ │ │ └── Tlv.ts │ │ ├── serialize │ │ │ ├── deserializeTlv.ts │ │ │ ├── getTlv.ts │ │ │ └── readTlvs.ts │ │ ├── util.ts │ │ └── validation │ │ │ └── validate.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── noise │ ├── .eslintrc │ ├── README.md │ ├── __integration__ │ │ └── ping-pong.spec.ts │ ├── __tests__ │ │ ├── noise-server.spec.ts │ │ ├── noise-socket.spec.ts │ │ └── noise-state.spec.ts │ ├── lib │ │ ├── handshake-state.ts │ │ ├── index.ts │ │ ├── noise-error.ts │ │ ├── noise-server-listen-options.ts │ │ ├── noise-server-options.ts │ │ ├── noise-server.ts │ │ ├── noise-socket-options.ts │ │ ├── noise-socket.ts │ │ ├── noise-state-options.ts │ │ ├── noise-state.ts │ │ └── read-state.ts │ ├── noise │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── transport │ ├── README.md │ ├── __tests__ │ │ ├── irc │ │ │ ├── IrcManager.spec.ts │ │ │ ├── IrcOrderManager.spec.ts │ │ │ ├── data │ │ │ │ ├── ircd.key │ │ │ │ └── ircd.pem │ │ │ └── helpers.ts │ │ ├── tsconfig.json │ │ └── util.ts │ ├── docs │ │ └── api.md │ ├── lib │ │ ├── @types │ │ │ └── irc │ │ │ │ └── index.d.ts │ │ ├── index.ts │ │ └── irc │ │ │ ├── ChannelType.ts │ │ │ ├── ILogger.ts │ │ │ ├── IrcManager.ts │ │ │ ├── IrcOrderManager.ts │ │ │ ├── Servers.ts │ │ │ ├── WhitelistHandler.ts │ │ │ ├── crypto.ts │ │ │ └── index.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json └── wire │ ├── .eslintrc │ ├── README.md │ ├── __fixtures__ │ ├── block.txt │ ├── blockhash.txt │ ├── messages.txt │ └── utxo.txt │ ├── __tests__ │ ├── MessageFactory.spec.ts │ ├── Peer.spec.ts │ ├── PeerServer.spec.ts │ ├── PingPongState.spec.ts │ ├── ScriptUtils.spec.ts │ ├── _test-utils.ts │ ├── deserialize │ │ └── address │ │ │ ├── derserializeAddress.spec.ts │ │ │ ├── ipv4StringFromBuffer.spec.ts │ │ │ ├── ipv6StringFromBuffer.spec.ts │ │ │ └── torStringFromBuffer.spec.ts │ ├── domain │ │ ├── AddressIPv4.spec.ts │ │ ├── AddressIPv6.spec.ts │ │ ├── AddressTor2.spec.ts │ │ ├── AddressTor3.spec.ts │ │ └── checksum.spec.ts │ ├── gossip │ │ ├── ChannelRangeQuery.spec.ts │ │ ├── ChannelsQuery.spec.ts │ │ ├── GossipFilter.spec.ts │ │ ├── GossipManager.spec.ts │ │ ├── GossipMemoryStore.spec.ts │ │ ├── GossipPeer.spec.ts │ │ ├── GossipQueriesReceiver.spec.ts │ │ ├── GossipQueriesSync.spec.ts │ │ ├── GossipRelay.spec.ts │ │ └── GossipSyncWatcher.spec.ts │ ├── messages │ │ ├── AcceptChannelMessage.spec.ts │ │ ├── AnnouncementSignaturesMessage.spec.ts │ │ ├── ChannelAnnouncementMessage.spec.ts │ │ ├── ChannelUpdateMessage.spec.ts │ │ ├── ClosingSignedMessage.spec.ts │ │ ├── ErrorMessage.spec.ts │ │ ├── ExtendedChannelAnnouncementMessage.spec.ts │ │ ├── FundingCreatedMessage.spec.ts │ │ ├── FundingLockedMessage.spec.ts │ │ ├── FundingSignedMessage.spec.ts │ │ ├── GossipTimestampFilterMessage.spec.ts │ │ ├── InitMessage.spec.ts │ │ ├── NodeAnnouncementMessage.spec.ts │ │ ├── OpenChannelMessage.spec.ts │ │ ├── PingMessage.spec.ts │ │ ├── PongMessage.spec.ts │ │ ├── QueryChannelRangeMessage.spec.ts │ │ ├── QueryShortChannelIdsMessage.spec.ts │ │ ├── ReplyChannelRangeMessage.spec.ts │ │ ├── ReplyShortChannelIdsEndMessage.spec.ts │ │ └── ShutdownChannelMessage.spec.ts │ └── serialize │ │ ├── address │ │ ├── ipv4StringToBuffer.spec.ts │ │ ├── ipv6StringToBuffer.spec.ts │ │ ├── serializeAddress.spec.ts │ │ └── torStringToBuffer.spec.ts │ │ └── readTlvs.spec.ts │ ├── lib │ ├── Constants.ts │ ├── MessageFactory.ts │ ├── MessageType.ts │ ├── Peer.ts │ ├── PeerServer.ts │ ├── PeerState.ts │ ├── PingPongState.ts │ ├── ScriptUtils.ts │ ├── WireError.ts │ ├── deserialize │ │ └── address │ │ │ ├── deserializeAddress.ts │ │ │ ├── deserializeIPv4.ts │ │ │ ├── deserializeIPv6.ts │ │ │ ├── deserializeTor2.ts │ │ │ ├── deserializeTor3.ts │ │ │ ├── ipv4StringFromBuffer.ts │ │ │ ├── ipv6StringFromBuffer.ts │ │ │ └── torStringFromBuffer.ts │ ├── domain │ │ ├── Address.ts │ │ ├── AddressIPv4.ts │ │ ├── AddressIPv6.ts │ │ ├── AddressJson.ts │ │ ├── AddressTor2.ts │ │ ├── AddressTor3.ts │ │ ├── AddressType.ts │ │ ├── Checksum.ts │ │ └── NetworkType.ts │ ├── flags │ │ ├── ChanneUpdateChannelFlags.ts │ │ ├── ChannelFeatureFlags.ts │ │ ├── ChannelUpdateMessageFlags.ts │ │ ├── InitFeatureFlags.ts │ │ ├── NodeFeatureFlags.ts │ │ ├── OpenChannelFlags.ts │ │ ├── QueryChannelRangeFlags.ts │ │ └── QueryScidFlags.ts │ ├── gossip │ │ ├── ChannelRangeQuery.ts │ │ ├── ChannelsQuery.ts │ │ ├── GossipEmitter.ts │ │ ├── GossipError.ts │ │ ├── GossipFilter.ts │ │ ├── GossipManager.ts │ │ ├── GossipMemoryStore.ts │ │ ├── GossipPeer.ts │ │ ├── GossipQueriesReceiver.ts │ │ ├── GossipQueriesSync.ts │ │ ├── GossipRelay.ts │ │ ├── GossipStore.ts │ │ ├── GossipSyncWatcher.ts │ │ └── IGossipFilterChainClient.ts │ ├── index.ts │ ├── messages │ │ ├── AcceptChannelMessage.ts │ │ ├── AnnouncementSignaturesMessage.ts │ │ ├── ChannelAnnouncementMessage.ts │ │ ├── ChannelUpdateMessage.ts │ │ ├── ClosingSignedMessage.ts │ │ ├── ErrorMessage.ts │ │ ├── ExtendedChannelAnnouncementMessage.ts │ │ ├── FundingCreatedMessage.ts │ │ ├── FundingLockedMessage.ts │ │ ├── FundingSignedMessage.ts │ │ ├── GossipTimestampFilterMessage.ts │ │ ├── IWireMessage.ts │ │ ├── InitMessage.ts │ │ ├── NodeAnnouncementMessage.ts │ │ ├── OpenChannelMessage.ts │ │ ├── PingMessage.ts │ │ ├── PongMessage.ts │ │ ├── QueryChannelRangeMessage.ts │ │ ├── QueryShortChannelIdsMessage.ts │ │ ├── ReplyChannelRangeMessage.ts │ │ ├── ReplyShortChannelIdsEndMessage.ts │ │ └── ShutdownMessage.ts │ └── serialize │ │ ├── Encoder.ts │ │ ├── EncodingType.ts │ │ ├── ZlibEncoder.ts │ │ ├── address │ │ ├── ipv4StringToBuffer.ts │ │ ├── ipv6StringToBuffer.ts │ │ ├── serializeAddress.ts │ │ ├── serializeIPv4.ts │ │ ├── serializeIPv6.ts │ │ ├── serializeTor2.ts │ │ ├── serializeTor3.ts │ │ └── torStringToBuffer.ts │ │ └── readTlvs.ts │ ├── package.json │ ├── tsconfig-build.json │ └── tsconfig.json ├── tsconfig.eslint.json ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | **/dist 6 | # don't lint nyc coverage output 7 | coverage 8 | # don't lint checksum test files 9 | build 10 | packages/checksum/__tests__/**/*.ts 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'] 7 | }, 8 | plugins: [ 9 | '@typescript-eslint', 10 | 'prettier', 11 | 'simple-import-sort' 12 | ], 13 | extends: [ 14 | 'eslint:recommended', 15 | 'plugin:@typescript-eslint/recommended', 16 | 'prettier' 17 | ], 18 | rules: { 19 | 'prettier/prettier': 'error', 20 | 'simple-import-sort/imports': 'error', 21 | 'no-console': ['error', { allow: ['error'] }], 22 | }, 23 | overrides: [ 24 | { 25 | // Disable TypeScript parsing for checksum test files 26 | files: ['packages/checksum/__tests__/**/*.ts'], 27 | extends: ['eslint:recommended'], 28 | rules: { 29 | 'prettier/prettier': 'error', 30 | 'no-undef': 'off', 31 | 'no-unused-vars': 'off' 32 | } 33 | }, 34 | { 35 | // Apply this rule to all files 36 | files: ['**/*'], 37 | rules: { 38 | 'no-restricted-syntax': [ 39 | 'error', 40 | { 41 | selector: 'Literal[bigint]', 42 | message: 43 | 'Avoid using bigint literals. Please use BigInt function notation instead. E.g., BigInt("123") instead of 123n.', 44 | }, 45 | ], 46 | }, 47 | }, 48 | { 49 | // Exclude this rule for test files (*.test.js, *.test.ts, *.spec.js, *.spec.ts) 50 | files: ['**/*.test.js', '**/*.test.ts', '**/*.spec.js', '**/*.spec.ts'], 51 | rules: { 52 | 'no-restricted-syntax': 'off', 53 | }, 54 | }, 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: node-dlc workflows 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x, 20.x] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Python 3.10 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.10' 25 | 26 | - name: Setup Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: 'yarn' 31 | 32 | - name: Install system dependencies 33 | run: sudo apt-get update && sudo apt-get install -y libzmq3-dev build-essential 34 | 35 | - name: Install dependencies 36 | run: yarn install --frozen-lockfile 37 | 38 | - name: Bootstrap packages 39 | run: yarn bootstrap 40 | 41 | - name: Run tests 42 | run: yarn test 43 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | }; 5 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | install: 2 | yarn && yarn bootstrap 3 | 4 | bootstrap: 5 | yarn bootstrap 6 | 7 | build: 8 | yarn build 9 | 10 | test: 11 | yarn test 12 | 13 | lint: 14 | yarn lint 15 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.24.1" 6 | } 7 | -------------------------------------------------------------------------------- /packages/bitcoin/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/bitcoin 2 | 3 | ![Build Status](https://github.com/AtomicFinance/node-dlc/actions/workflows/main.yml/badge.svg) 4 | [![Standard Code Style](https://img.shields.io/badge/codestyle-standard-brightgreen.svg)](https://github.com/standard/standard) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](../../LICENSE) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i @node-dlc/bitcoin 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/bitcoin/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/bitcoin/bitcoin: -------------------------------------------------------------------------------- 1 | ../../../bitcoin -------------------------------------------------------------------------------- /packages/bitcoin/lib/Base58.ts: -------------------------------------------------------------------------------- 1 | import { bigToBufBE } from '@node-dlc/bufio'; 2 | 3 | /** 4 | * Returns a tuple containing the quotient and remainder when the divident (num) is 5 | * divided by the divisor (mod). 6 | * @param num divident 7 | * @param mod divisor 8 | */ 9 | export function divmod(num: bigint, mod: bigint): [bigint, bigint] { 10 | return [num / mod, num % mod]; 11 | } 12 | 13 | /** 14 | * Base58 encoding and decoding utility 15 | */ 16 | export class Base58 { 17 | public static alphabet = 18 | '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 19 | 20 | /** 21 | * Encodes a buffer into a base58 string 22 | */ 23 | public static encode(buf: Buffer): string { 24 | let n = BigInt('0x' + buf.toString('hex')); 25 | 26 | // encode into base58 27 | let str = ''; 28 | while (n > BigInt(0)) { 29 | const mod = n % BigInt(58); 30 | str = Base58.alphabet[Number(mod)] + str; 31 | n = n / BigInt(58); 32 | } 33 | 34 | // add leading zeros 35 | for (const val of buf) { 36 | if (val !== 0) break; 37 | str = '1' + str; 38 | } 39 | 40 | return str; 41 | } 42 | 43 | /** 44 | * Decodes the base58 string into a buffer 45 | */ 46 | public static decode(str: string): Buffer { 47 | // determine leading zero bytes which will be prepended 48 | let prefix = 0; 49 | for (let i = 0; i < str.length; i++) { 50 | if (str[i] === '1') prefix += 1; 51 | else break; 52 | } 53 | 54 | // decode from base58 55 | let n = BigInt(0); 56 | for (const char of str) { 57 | n *= BigInt(58); 58 | n += BigInt(Base58.alphabet.indexOf(char)); 59 | } 60 | 61 | return Buffer.concat([Buffer.alloc(prefix), bigToBufBE(n)]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/Base58Check.ts: -------------------------------------------------------------------------------- 1 | import { hash256 } from '@node-dlc/crypto'; 2 | 3 | import { Base58 } from './Base58'; 4 | import { BitcoinError } from './BitcoinError'; 5 | import { BitcoinErrorCode } from './BitcoinErrorCode'; 6 | 7 | export class Base58Check { 8 | /** 9 | * Perform a base58 encoding by appends a 4-byte hash256 checksum 10 | * at the end of the value. 11 | * @param buf 12 | */ 13 | public static encode(buf: Buffer): string { 14 | return Base58.encode(Buffer.concat([buf, hash256(buf).slice(0, 4)])); 15 | } 16 | 17 | /** 18 | * Decodes a base58 check value. Throws error if checksum is invalid 19 | * @param buf 20 | */ 21 | public static decode(input: string): Buffer { 22 | const total = Base58.decode(input); 23 | const data = total.slice(0, total.length - 4); 24 | const checksum = total.slice(total.length - 4); 25 | const hash = hash256(data).slice(0, 4); 26 | if (!hash.equals(checksum)) { 27 | throw new BitcoinError(BitcoinErrorCode.Base58ChecksumFailed, { 28 | actual: hash, 29 | expected: checksum, 30 | }); 31 | } 32 | return data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/BitcoinError.ts: -------------------------------------------------------------------------------- 1 | import { BitcoinErrorCode } from './BitcoinErrorCode'; 2 | 3 | function getMessage(code: BitcoinErrorCode) { 4 | switch (code) { 5 | case BitcoinErrorCode.Base58ChecksumFailed: 6 | return 'Base58Check checksum failed'; 7 | case BitcoinErrorCode.PubKeyInvalid: 8 | return 'Invalid pubkey'; 9 | case BitcoinErrorCode.PubKeyHashInvalid: 10 | return 'Invalid pubkeyhash'; 11 | case BitcoinErrorCode.SigEncodingInvalid: 12 | return 'Signatures requires BIP66 DER encoding'; 13 | case BitcoinErrorCode.SigHashTypeInvalid: 14 | return 'Invalid Signature SIGHASH type'; 15 | case BitcoinErrorCode.MultiSigSetupInvalid: 16 | return 'MultiSig structure is invalid'; 17 | case BitcoinErrorCode.Hash160Invalid: 18 | return 'Hash160 requires 20-byte Buffer'; 19 | case BitcoinErrorCode.Hash256Invalid: 20 | return 'return Hash256 requires 32-byte Buffer'; 21 | default: 22 | return 'Unknown'; 23 | } 24 | } 25 | 26 | export class BitcoinError extends Error { 27 | constructor(readonly code: BitcoinErrorCode, readonly data?: any) { 28 | super(getMessage(code)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/BitcoinErrorCode.ts: -------------------------------------------------------------------------------- 1 | export enum BitcoinErrorCode { 2 | Base58ChecksumFailed, 3 | PubKeyInvalid, 4 | PubKeyHashInvalid, 5 | SigEncodingInvalid, 6 | SigHashTypeInvalid, 7 | MultiSigSetupInvalid, 8 | Hash160Invalid, 9 | Hash256Invalid, 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/Block.ts: -------------------------------------------------------------------------------- 1 | import { Tx } from './Tx'; 2 | 3 | export type Block = { 4 | hash: string; 5 | strippedsize: number; 6 | size: number; 7 | weight: number; 8 | height: number; 9 | version: number; 10 | versionHex: string; 11 | merkelroot: string; 12 | time: number; 13 | mediantime: number; 14 | chainwork: string; 15 | previousblockhash: string; 16 | nextblockhash: string; 17 | bits: string; 18 | difficulty: string; 19 | txs: Tx[]; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/HashByteOrder.ts: -------------------------------------------------------------------------------- 1 | export enum HashByteOrder { 2 | /** 3 | * Internal byte order is the natural byte order of elements as they 4 | * cedome out of hash function. Calculations are often performed 5 | * using the internal byte order. 6 | * 7 | * This is often referred to as little-endian byte-order due to the 8 | * block hash being reversed to compare it to the target. 9 | */ 10 | Internal, 11 | 12 | /** 13 | * RPC byte order is the byte order of hash value as displayed in 14 | * the bitcoind RPC. Most hash values associated with Bitcoin are 15 | * displayed in this order by reversing the internal byte order. 16 | * 17 | * This is often referred to as big-endian byte-order due to the 18 | * block hash being reversed to compare it to the target. 19 | */ 20 | RPC, 21 | } 22 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/ICloneable.ts: -------------------------------------------------------------------------------- 1 | export interface ICloneable { 2 | clone(): T; 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/LexicographicalSorters.ts: -------------------------------------------------------------------------------- 1 | import { HashByteOrder } from './HashByteOrder'; 2 | import { TxIn } from './TxIn'; 3 | import { TxOut } from './TxOut'; 4 | 5 | /** 6 | * Compares two transaction inputs and lexicographically sorted in 7 | * ascending order using the reversed byte order (RPC) of the txids. 8 | * If the same txids are in both TxIn, then the output index is sorted 9 | * in ascending order. 10 | * @param a 11 | * @param b 12 | */ 13 | export function bip69InputSorter(a: TxIn, b: TxIn): number { 14 | const txid = a.outpoint.txid 15 | .serialize(HashByteOrder.RPC) 16 | .compare(b.outpoint.txid.serialize()); 17 | if (txid !== 0) return txid; 18 | 19 | if (a.outpoint.outputIndex < b.outpoint.outputIndex) return -1; 20 | if (a.outpoint.outputIndex > b.outpoint.outputIndex) return 1; 21 | return 0; 22 | } 23 | 24 | /** 25 | * Sort transaction amounts in ascending order. Then ScriptPubKey values 26 | * will be sorted in ascending byte order next. 27 | * @param a 28 | * @param b 29 | */ 30 | export function bip69OutputSorter(a: TxOut, b: TxOut): number { 31 | // sort by amount desc first 32 | const amount = Number(a.value.sats - b.value.sats); 33 | if (amount !== 0) return amount; 34 | 35 | // then sort by script pub key 36 | return a.scriptPubKey.serializeCmds().compare(b.scriptPubKey.serializeCmds()); 37 | } 38 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/ScriptCmd.ts: -------------------------------------------------------------------------------- 1 | import { OpCode } from './OpCodes'; 2 | 3 | export type ScriptCmd = OpCode | Buffer; 4 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/SigHashType.ts: -------------------------------------------------------------------------------- 1 | export enum SigHashType { 2 | SIGHASH_ALL = 0x01, 3 | SIGHASH_NONE = 0x02, 4 | SIGHASH_SINGLE = 0x03, 5 | SIGHASH_ANYONECANPAY = 0x80, 6 | } 7 | 8 | export function isSigHashTypeValid(type: SigHashType): boolean { 9 | if (type === SigHashType.SIGHASH_ALL) return true; 10 | if (type === SigHashType.SIGHASH_NONE) return true; 11 | if (type === SigHashType.SIGHASH_SINGLE) return true; 12 | if (type === (SigHashType.SIGHASH_ALL | SigHashType.SIGHASH_ANYONECANPAY)) 13 | return true; 14 | if (type === (SigHashType.SIGHASH_NONE | SigHashType.SIGHASH_ANYONECANPAY)) 15 | return true; 16 | if (type === (SigHashType.SIGHASH_SINGLE | SigHashType.SIGHASH_ANYONECANPAY)) 17 | return true; 18 | return false; 19 | } 20 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/SizeResult.ts: -------------------------------------------------------------------------------- 1 | export type SizeResult = { 2 | size: number; 3 | vsize: number; 4 | weight: number; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/Sorter.ts: -------------------------------------------------------------------------------- 1 | export type Sorter = (a: T, b: T) => number; 2 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/TimeLockMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Specifies what a time lock is based on. Currently this can either be 3 | * a block-based or timestamp-based time lock and is used for absolute 4 | * time locks (nLocktime) or relative time locks (nSequence). 5 | */ 6 | export enum TimeLockMode { 7 | /** 8 | * Block height based time lock 9 | */ 10 | Block, 11 | 12 | /** 13 | * Unix timestamps based time lock 14 | */ 15 | Time, 16 | } 17 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/Witness.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter, StreamReader } from '@node-dlc/bufio'; 2 | 3 | import { ICloneable } from './ICloneable'; 4 | 5 | /** 6 | * Represents segregated witness data. This data is nothing more than a 7 | * buffer. 8 | */ 9 | export class Witness implements ICloneable { 10 | /** 11 | * Parses witness data 12 | * @param reader 13 | */ 14 | public static parse(reader: StreamReader): Witness { 15 | const len = reader.readVarInt(); 16 | const data = reader.readBytes(Number(len)); 17 | return new Witness(data); 18 | } 19 | 20 | /** 21 | * Hex-encoded Witness data. This method interally creates a 22 | * StreamReader and uses the parse function 23 | * @param hex encoded as a string 24 | */ 25 | public static fromHex(hex: string): Witness { 26 | return Witness.parse(StreamReader.fromHex(hex)); 27 | } 28 | 29 | constructor(readonly data: Buffer) {} 30 | 31 | /** 32 | * Serializes witness data into a buffer in the format: 33 | * 34 | * [varint] length 35 | * [length] data 36 | */ 37 | public serialize(): Buffer { 38 | const writer = new BufferWriter(); 39 | writer.writeVarInt(this.data.length); 40 | writer.writeBytes(this.data); 41 | return writer.toBuffer(); 42 | } 43 | 44 | /** 45 | * Returns the string of a piece of witness data 46 | */ 47 | public toString() { 48 | return this.data.toString('hex'); 49 | } 50 | 51 | /** 52 | * Returns the string of a piece of witness data 53 | */ 54 | public toJSON() { 55 | return this.toString(); 56 | } 57 | 58 | /** 59 | * Clone via deep copy 60 | */ 61 | public clone(): Witness { 62 | return new Witness(Buffer.from(this.data)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base58'; 2 | export * from './Base58Check'; 3 | export * from './BitcoinError'; 4 | export * from './BitcoinErrorCode'; 5 | export * from './Block'; 6 | export * from './HashByteOrder'; 7 | export * from './HashValue'; 8 | export * from './LexicographicalSorters'; 9 | export * from './LockTime'; 10 | export * from './OpCodes'; 11 | export * from './OutPoint'; 12 | export * from './Script'; 13 | export * from './ScriptCmd'; 14 | export * from './Sequence'; 15 | export * from './SigHashType'; 16 | export * from './SizeResult'; 17 | export * from './Sorter'; 18 | export * from './Stack'; 19 | export * from './TimeLockMode'; 20 | export * from './Tx'; 21 | export * from './TxBuilder'; 22 | export * from './TxIn'; 23 | export * from './TxOut'; 24 | export * from './Value'; 25 | export * from './Wif'; 26 | export * from './Witness'; 27 | export * from './Amount'; 28 | export * from './ICloneable'; 29 | -------------------------------------------------------------------------------- /packages/bitcoin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/bitcoin", 3 | "version": "0.24.1", 4 | "description": "DLC bitcoin", 5 | "scripts": { 6 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 7 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 8 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 10 | "prepublish": "npm run build" 11 | }, 12 | "keywords": [ 13 | "dlc", 14 | "bitcoin" 15 | ], 16 | "author": "Atomic Finance ", 17 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/bitcoin", 18 | "license": "MIT", 19 | "main": "dist/index.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 23 | }, 24 | "dependencies": { 25 | "@node-dlc/bufio": "^0.24.1", 26 | "@node-dlc/crypto": "^0.24.1" 27 | }, 28 | "publishConfig": { 29 | "access": "public" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/bitcoin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "skipLibCheck": true 6 | }, 7 | "include": ["./lib"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/bitcoind/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/bitcoind/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/bitcoind 2 | 3 | This package provides connectivity to a bitcoind node by enabling RPC functions 4 | and Zeromq streaming. 5 | 6 | This package has an external dependency on the 7 | [zeromq](https://www.npmjs.com/package/zeromq) package. 8 | 9 | ## Example Usage 10 | 11 | You can connect to a bitcoind instance by providing rpc and zeromq options. 12 | 13 | ```typescript 14 | const bitciondOptions = { 15 | host: "127.0.0.1", 16 | port: 8333, 17 | rpcuser: "user", 18 | rpcpassword: "pass", 19 | zmqpubrawtx: "tcp://127.0.0.1:18332" 20 | zmqpubrawblock: "tcp://127.0.0.1:18333"; 21 | } 22 | const client = new BitcoindClient(bitcoindOptions); 23 | ``` 24 | 25 | You can subscribe to raw transactions and blocks emitted by zeromq: 26 | 27 | ```typescript 28 | client.subscribeRawTx(); 29 | client.on("rawtx", (rawtx: Buffer) => { 30 | // deserialize and do something 31 | }); 32 | ``` 33 | 34 | ```typescript 35 | client.subscribeRawBlock(); 36 | client.on("rawblock", (rawblock: Buffer) => { 37 | // deserialize and do something 38 | }); 39 | ``` 40 | 41 | You can call RPC functions: 42 | 43 | ```typescript 44 | // blockchain info 45 | await client.getBlockchainInfo(); 46 | 47 | // returns the block hash for a height 48 | await client.getBlockHash(0); 49 | 50 | // returns a BlockSummary 51 | await client.getBlock("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); 52 | 53 | // returns a Buffer 54 | await client.getRawBlock("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); 55 | 56 | // returns a Transaction 57 | await client.getTransaction("aa5f3068b53941915d82be382f2b35711305ec7d454a34ca69f8897510db7ab8"); 58 | 59 | // returns a Buffer 60 | await client.getRawTransaction("aa5f3068b53941915d82be382f2b35711305ec7d454a34ca69f8897510db7ab8"); 61 | 62 | // returns a Utxo 63 | await client.getUtxo("aa5f3068b53941915d82be382f2b35711305ec7d454a34ca69f8897510db7ab8", 0); 64 | ``` 65 | -------------------------------------------------------------------------------- /packages/bitcoind/__tests__/policies/constant-backoff.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import sinon from "sinon"; 3 | import { ConstantBackoff } from "../../lib/policies/constant-backoff"; 4 | 5 | describe("ConstantBackoff", () => { 6 | it("single call", async () => { 7 | const stub = sinon.stub(); 8 | const sut = new ConstantBackoff(1000, stub); 9 | await sut.backoff(); 10 | expect(stub.args[0][0]).to.equal(1000); 11 | }); 12 | 13 | it("multiple class", async () => { 14 | const stub = sinon.stub(); 15 | const sut = new ConstantBackoff(100, stub); 16 | await sut.backoff(); 17 | await sut.backoff(); 18 | await sut.backoff(); 19 | expect(stub.args[0][0]).to.equal(100); 20 | expect(stub.args[1][0]).to.equal(100); 21 | expect(stub.args[2][0]).to.equal(100); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/bitcoind/__tests__/policies/exponential-backoff.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import sinon from "sinon"; 3 | import { ExponentialBackoff } from "../../lib/policies/exponential-backoff"; 4 | 5 | describe("ExponentialBackoff", () => { 6 | it("single call", async () => { 7 | const stub = sinon.stub(); 8 | const sut = new ExponentialBackoff(1000, 2, stub); 9 | await sut.backoff(); 10 | expect(stub.args[0][0]).to.equal(1000); 11 | }); 12 | 13 | it("multiple class", async () => { 14 | const stub = sinon.stub(); 15 | const sut = new ExponentialBackoff(1000, 2, stub); 16 | await sut.backoff(); 17 | await sut.backoff(); 18 | await sut.backoff(); 19 | await sut.backoff(); 20 | expect(stub.args[0][0]).to.equal(1000); 21 | expect(stub.args[1][0]).to.equal(2000); 22 | expect(stub.args[2][0]).to.equal(4000); 23 | expect(stub.args[3][0]).to.equal(8000); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/bitcoind/__tests__/policies/wait.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { wait } from "../../lib/policies/wait"; 3 | 4 | describe("wait", () => { 5 | it("waits", async () => { 6 | const start = Date.now(); 7 | await wait(100); 8 | const end = Date.now(); 9 | expect(end - start).to.be.gte(100); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/bitcoind-options.ts: -------------------------------------------------------------------------------- 1 | import { IPolicy } from './policies/policy'; 2 | 3 | export interface IBitcoindOptions { 4 | rpcuser?: string; 5 | rpcpassword?: string; 6 | host: string; 7 | port: number; 8 | zmqpubrawtx?: string; 9 | zmqpubrawblock?: string; 10 | policyMaker?: () => IPolicy; 11 | } 12 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bitcoind-client'; 2 | export * from './jsonrpc-error'; 3 | export * from './policies/backoff-strategy'; 4 | export * from './policies/constant-backoff'; 5 | export * from './policies/exponential-backoff'; 6 | export * from './policies/policy'; 7 | export * from './policies/retry-policy'; 8 | export * from './types/block-chain-info'; 9 | export * from './types/BlockHeader'; 10 | export * from './types/blocksummary'; 11 | export * from './types/transaction'; 12 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/jsonrpc-error.ts: -------------------------------------------------------------------------------- 1 | export class JsonRpcError extends Error { 2 | public statusCode: number; 3 | public body: string; 4 | 5 | constructor(statusCode: number, body: string) { 6 | super('Request failed'); 7 | this.statusCode = statusCode; 8 | this.body = body; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/jsonrpc-request.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | 3 | import { IBitcoindOptions } from './bitcoind-options'; 4 | import { JsonRpcError } from './jsonrpc-error'; 5 | 6 | export function jsonrpcRequest( 7 | method: string, 8 | params: any = [], 9 | id: number, 10 | opts: IBitcoindOptions, 11 | ): Promise { 12 | return new Promise((resolve, reject) => { 13 | const { host, port, rpcuser: rpcUser, rpcpassword: rpcPassword } = opts; 14 | const body = JSON.stringify({ 15 | id, 16 | jsonrpc: '1.0', 17 | method, 18 | params, 19 | }); 20 | const req = http.request( 21 | { 22 | auth: `${rpcUser}:${rpcPassword}`, 23 | headers: { 24 | 'content-length': body.length, 25 | 'content-type': 'text/plain', 26 | }, 27 | host, 28 | method: 'POST', 29 | port, 30 | }, 31 | (res) => { 32 | const buffers = []; 33 | res.on('error', reject); 34 | res.on('data', (buf) => buffers.push(buf)); 35 | res.on('end', () => { 36 | const isJson = res.headers['content-type'] === 'application/json'; 37 | const raw = Buffer.concat(buffers).toString(); 38 | const result = isJson ? JSON.parse(raw) : raw; 39 | if (res.statusCode === 200) { 40 | resolve(result.result); 41 | } else { 42 | reject(new JsonRpcError(res.statusCode, result)); 43 | } 44 | }); 45 | }, 46 | ); 47 | req.on('error', reject); 48 | req.end(body); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/backoff-strategy.ts: -------------------------------------------------------------------------------- 1 | export interface IBackoffStrategy { 2 | backoff(): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/constant-backoff.ts: -------------------------------------------------------------------------------- 1 | import { IBackoffStrategy } from './backoff-strategy'; 2 | import { wait } from './wait'; 3 | 4 | export class ConstantBackoff implements IBackoffStrategy { 5 | constructor( 6 | readonly timeoutMs: number, 7 | readonly waitFn: (timeoutMs: number) => Promise = wait, 8 | ) {} 9 | 10 | public async backoff(): Promise { 11 | return await this.waitFn(this.timeoutMs); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/exponential-backoff.ts: -------------------------------------------------------------------------------- 1 | import { IBackoffStrategy } from './backoff-strategy'; 2 | import { wait } from './wait'; 3 | 4 | /** 5 | * Exponential backoff strategy that when supplied with a startMs value will 6 | * increment the timeout based on powers of the base. It uses the general 7 | * exponential function equation: 8 | * 9 | * ``` 10 | * timeout = ab^x 11 | * ``` 12 | */ 13 | export class ExponentialBackoff implements IBackoffStrategy { 14 | public timeout: number; 15 | public exp: number; 16 | 17 | constructor( 18 | readonly startMs: number, 19 | readonly base: number, 20 | readonly waitFn: (timeoutMs: number) => Promise = wait, 21 | ) { 22 | this.exp = 0; 23 | } 24 | 25 | public async backoff(): Promise { 26 | const timeout = this.startMs * Math.pow(this.base, this.exp); 27 | await this.waitFn(timeout); 28 | this.exp++; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/policy.ts: -------------------------------------------------------------------------------- 1 | export interface IPolicy { 2 | execute(fn: () => Promise): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/retry-policy.ts: -------------------------------------------------------------------------------- 1 | import { IBackoffStrategy } from './backoff-strategy'; 2 | import { IPolicy } from './policy'; 3 | 4 | export class RetryPolicy implements IPolicy { 5 | private numFailures: number; 6 | private lastFailure: Error; 7 | 8 | constructor( 9 | readonly maxFailures: number, 10 | readonly backoffStrategy: IBackoffStrategy, 11 | ) { 12 | this.numFailures = 0; 13 | } 14 | 15 | public async execute(fn: () => Promise): Promise { 16 | while (this.numFailures < this.maxFailures) { 17 | try { 18 | return await fn(); 19 | } catch (ex) { 20 | this.numFailures += 1; 21 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 22 | this.lastFailure = ex; 23 | await this.backoffStrategy.backoff(); 24 | } 25 | } 26 | throw this.lastFailure; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/policies/wait.ts: -------------------------------------------------------------------------------- 1 | export function wait(timeout: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, timeout)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/types/BlockHeader.ts: -------------------------------------------------------------------------------- 1 | export type BlockHeader = { 2 | hash: string; 3 | confirmations: number; 4 | height: number; 5 | version: number; 6 | versionHex: string; 7 | merkleroot: string; 8 | time: number; 9 | mediantime: number; 10 | nonce: number; 11 | bits: string; 12 | difficulty: number; 13 | chainwork: string; 14 | nTx: number; 15 | previousblockhash: string; 16 | nextblockhash: string; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/types/block-chain-info.ts: -------------------------------------------------------------------------------- 1 | export type BlockChainInfo = { 2 | chain: string; 3 | blocks: number; 4 | headers: number; 5 | bestblockhash: string; 6 | difficulty: number; 7 | mediantime: number; 8 | verificationprogress: number; 9 | intialblockdownload: boolean; 10 | chainwork: string; 11 | size_on_disk: number; 12 | pruned: boolean; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/types/blocksummary.ts: -------------------------------------------------------------------------------- 1 | export type BlockSummary = { 2 | hash: string; 3 | confirmations: number; 4 | size: number; 5 | weight: number; 6 | height: number; 7 | version: number; 8 | versionHex: string; 9 | merkleroot: string; 10 | tx: string[]; 11 | time: number; 12 | mediantime: number; 13 | nonce: number; 14 | bits: string; 15 | difficulty: string; 16 | chainwork: string; 17 | nTx: number; 18 | previousblockhash: string; 19 | nextblockhash: string; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/types/transaction.ts: -------------------------------------------------------------------------------- 1 | export type Transaction = { 2 | txid: string; 3 | hash: string; 4 | version: number; 5 | size: number; 6 | vsize: number; 7 | weight: number; 8 | locktime: number; 9 | vin: Input[]; 10 | vout: Output[]; 11 | hex: string; 12 | blockhash: string; 13 | confirmations: number; 14 | time: number; 15 | blocktime: number; 16 | }; 17 | 18 | export type Input = { 19 | txid: string; 20 | vout: number; 21 | scriptSig: ScriptSig; 22 | sequence: number; 23 | }; 24 | 25 | export type Output = { 26 | value: number; 27 | n: number; 28 | scriptPubKey: ScriptPubKey; 29 | }; 30 | 31 | export type ScriptSig = { 32 | asm: string; 33 | hex: string; 34 | }; 35 | 36 | export type ScriptPubKey = { 37 | asm: string; 38 | hex: string; 39 | type: string; 40 | reqSigs?: number; 41 | addresses?: string[]; 42 | }; 43 | 44 | export type Utxo = { 45 | bestblock: string; 46 | confirmations: string; 47 | value: number; 48 | scriptPubKey: ScriptPubKey; 49 | coinbase: boolean; 50 | }; 51 | -------------------------------------------------------------------------------- /packages/bitcoind/lib/wait.ts: -------------------------------------------------------------------------------- 1 | export function wait(ms: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/bitcoind/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/bitcoind", 3 | "version": "0.24.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@node-dlc/bitcoind", 9 | "version": "0.24.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "zeromq": "^6.4.2" 13 | } 14 | }, 15 | "node_modules/cmake-ts": { 16 | "version": "0.6.1", 17 | "resolved": "https://registry.npmjs.org/cmake-ts/-/cmake-ts-0.6.1.tgz", 18 | "integrity": "sha512-uUn2qGhf20j8W/sQ7+UnvvqO1zNccqgbLgwRJi7S23FsjMWJqxvKK80Vc+tvLNKfpJzwH0rgoQD1l24SMnX0yg==", 19 | "license": "MIT", 20 | "bin": { 21 | "cmake-ts": "build/main.js" 22 | } 23 | }, 24 | "node_modules/node-addon-api": { 25 | "version": "8.3.1", 26 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", 27 | "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", 28 | "license": "MIT", 29 | "engines": { 30 | "node": "^18 || ^20 || >= 21" 31 | } 32 | }, 33 | "node_modules/zeromq": { 34 | "version": "6.4.2", 35 | "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.4.2.tgz", 36 | "integrity": "sha512-FnQlI4lEAewE4JexJ6kqQuBVzRf0Mg1n/qE3uXilfosf+X5lqJPiaYfdL/w4SzgAEVBTyqbMt9NbjwI5H89Yaw==", 37 | "hasInstallScript": true, 38 | "license": "MIT AND MPL-2.0", 39 | "dependencies": { 40 | "cmake-ts": "^0.6.1", 41 | "node-addon-api": "^8.3.0" 42 | }, 43 | "engines": { 44 | "node": ">= 12" 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/bitcoind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/bitcoind", 3 | "version": "0.24.1", 4 | "description": "Basic bitcoind JSONRPC and ZMQ client", 5 | "keywords": [], 6 | "author": "Brian Mancini ", 7 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/bitcoind", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 12 | }, 13 | "main": "dist/index.js", 14 | "scripts": { 15 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*s\"", 16 | "lint": "../../node_modules/.bin/eslint lib --ext .ts,.js", 17 | "lint:fix": "../../node_modules/.bin/eslint lib --ext .ts,.js --fix", 18 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 19 | "prepublish": "npm run build" 20 | }, 21 | "dependencies": { 22 | "zeromq": "^6.4.2" 23 | }, 24 | "publishConfig": { 25 | "access": "public" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/bitcoind/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/bitcoind/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/bufio/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/bigFromBufBE.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { bigFromBufBE } from "../lib/bigFromBufBE"; 3 | 4 | describe(".bigFromBufBE", () => { 5 | const tests: Array<[Buffer, bigint]> = [ 6 | [Buffer.from([0, 0, 0, 1]), 1n], 7 | [Buffer.from([1, 0, 0, 0]), 16777216n], 8 | ]; 9 | for (const test of tests) { 10 | it(`${test[0].toString("hex")} to ${test[1]}`, () => { 11 | expect(bigFromBufBE(test[0])).to.deep.equal(test[1]); 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/bigFromBufLE.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { bigFromBufLE } from "../lib/bigFromBufLE"; 3 | 4 | describe(".bigFromBufLE", () => { 5 | const tests: Array<[Buffer, bigint]> = [ 6 | [Buffer.from([1, 0, 0, 0]), 1n], 7 | [Buffer.from([, 0, 0, 1]), 16777216n], 8 | ]; 9 | for (const test of tests) { 10 | it(`${test[0].toString("hex")} to ${test[1]}`, () => { 11 | expect(bigFromBufLE(test[0])).to.deep.equal(test[1]); 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/bigToBufBE.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { bigToBufBE } from "../lib/bigToBufBE"; 3 | 4 | describe(".bigToBufBE", () => { 5 | const tests: Array<[[bigint, number], Buffer]> = [ 6 | [[1n, 4], Buffer.from([0, 0, 0, 1])], 7 | [[16777216n, 4], Buffer.from([1, 0, 0, 0])], 8 | [[16777216n, undefined], Buffer.from([1, 0, 0, 0])], 9 | ]; 10 | for (const test of tests) { 11 | it(`${test[0]} > ${test[1].toString("hex")}`, () => { 12 | expect(bigToBufBE(...test[0])).to.deep.equal(test[1]); 13 | }); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/bigToBufLE.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { bigToBufLE } from "../lib/bigToBufLE"; 3 | 4 | describe(".bigToBufLE", () => { 5 | const tests: Array<[bigint, Buffer]> = [ 6 | [1n, Buffer.from([1, 0, 0, 0])], 7 | [16777216n, Buffer.from([0, 0, 0, 1])], 8 | ]; 9 | for (const test of tests) { 10 | it(`${test[0]} > ${test[1].toString("hex")}`, () => { 11 | expect(bigToBufLE(test[0], 4)).to.deep.equal(test[1]); 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/encodeVarInt.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { encodeVarInt } from "../lib/encodeVarInt"; 3 | 4 | describe("Varint", () => { 5 | const tests: Array<[Buffer, bigint]> = [ 6 | [Buffer.from("00", "hex"), BigInt(0x00)], 7 | [Buffer.from("01", "hex"), BigInt(0x01)], 8 | [Buffer.from("fc", "hex"), BigInt(0xfc)], 9 | [Buffer.from("fd0001", "hex"), BigInt(0x0100)], 10 | [Buffer.from("fdffff", "hex"), BigInt(0xffff)], 11 | [Buffer.from("fe00000001", "hex"), BigInt(0x01000000)], 12 | [Buffer.from("feffffffff", "hex"), BigInt(0xffffffff)], 13 | [Buffer.from("ff0000000000000001", "hex"), BigInt("0x0100000000000000")], 14 | [Buffer.from("ffffffffffffffffff", "hex"), BigInt("0xffffffffffffffff")], 15 | ]; 16 | 17 | describe("encodeVarint", () => { 18 | for (const test of tests) { 19 | it(`${test[1]} => 0x${test[0].toString("hex")}`, () => { 20 | const actual = encodeVarInt(test[1]); 21 | expect(actual.toString("hex")).to.equal(test[0].toString("hex")); 22 | }); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/bufio/__tests__/varIntBytes.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { varIntBytes } from "../lib/varIntBytes"; 3 | 4 | describe(".varIntBytes()", () => { 5 | const tests: Array<[number | BigInt, number]> = [ 6 | [0, 1], 7 | [1, 1], 8 | [0xfc, 1], 9 | [0xfd, 3], 10 | [0xff, 3], 11 | [0xfffe, 3], 12 | [0xffff, 3], 13 | [0x010000, 5], 14 | [0xffffff, 5], 15 | [0xffffffff, 5], 16 | [0x0100000000, 9], 17 | [0x01ffffffff, 9], 18 | [BigInt("0xffffffffffffffff"), 9], 19 | ]; 20 | 21 | for (const [input, expected] of tests) { 22 | it(`${input} => ${expected}`, () => { 23 | expect(varIntBytes(input)).to.equal(expected); 24 | }); 25 | } 26 | 27 | it("throws when below zero", () => { 28 | expect(() => varIntBytes(-1)).to.throw(); 29 | }); 30 | 31 | it("throws when above uint64", () => { 32 | expect(() => varIntBytes(BigInt("0x01ffffffffffffffff"))).to.throw(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/bufio/bufio: -------------------------------------------------------------------------------- 1 | ../../../bufio -------------------------------------------------------------------------------- /packages/bufio/lib/bigFromBufBE.ts: -------------------------------------------------------------------------------- 1 | export function bigFromBufBE(buf: Buffer): bigint { 2 | return BigInt('0x' + buf.toString('hex')); 3 | } 4 | -------------------------------------------------------------------------------- /packages/bufio/lib/bigFromBufLE.ts: -------------------------------------------------------------------------------- 1 | import { bigFromBufBE } from './bigFromBufBE'; 2 | 3 | export function bigFromBufLE(buf: Buffer): bigint { 4 | return bigFromBufBE(Buffer.from(buf).reverse()); 5 | } 6 | -------------------------------------------------------------------------------- /packages/bufio/lib/bigToBufBE.ts: -------------------------------------------------------------------------------- 1 | export function bigToBufBE(num: bigint, len?: number): Buffer { 2 | let str = num.toString(16); 3 | if (len) str = str.padStart(len * 2, '0'); 4 | else if (str.length % 2 === 1) str = '0' + str; 5 | return Buffer.from(str, 'hex'); 6 | } 7 | -------------------------------------------------------------------------------- /packages/bufio/lib/bigToBufLE.ts: -------------------------------------------------------------------------------- 1 | import { bigToBufBE } from './bigToBufBE'; 2 | 3 | export function bigToBufLE(num: bigint, len?: number): Buffer { 4 | return bigToBufBE(num, len).reverse(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/bufio/lib/bufToStream.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | /** 4 | * Converts a buffer to a Readable stream. 5 | */ 6 | export function bufToStream(buf: Buffer): Readable { 7 | const result = new Readable(); 8 | result.push(buf); 9 | result.push(null); // ends the stream 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /packages/bufio/lib/encodeVarInt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Encodes a number into a VarInt Buffer which is an unsigned integer from 3 | * 0 to 2^64-1 occupying 1 to 9 bytes. The first byte indicates the variable 4 | * length and numbers read in little-endian. 5 | * < 0xfd 8-bit number 6 | * 0xfd: 16-bit LE number (3 bytes consumed) 7 | * 0xfe: 32-bit LE number (5 bytes consumed) 8 | * 0xff: 64-bit LE number (9 bytes consumed) 9 | * @param val 10 | */ 11 | export function encodeVarInt(i: bigint | number): Buffer { 12 | // 8-bit, 1-byte number 13 | if (i < BigInt('0xfd')) { 14 | return Buffer.from([Number(i)]); 15 | } 16 | // 16-bit, 2-byte number 17 | else if (i < BigInt('0x10000')) { 18 | const buf = Buffer.alloc(3); 19 | buf[0] = 0xfd; 20 | buf.writeUInt16LE(Number(i), 1); 21 | return buf; 22 | } 23 | // 32-bit, 4-byte number 24 | else if (i < BigInt('0x100000000')) { 25 | const buf = Buffer.alloc(5); 26 | buf[0] = 0xfe; 27 | buf.writeUInt32LE(Number(i), 1); 28 | return buf; 29 | } 30 | // 64-bit, 8-byte number 31 | else if (i < BigInt('0x10000000000000000')) { 32 | const buf = Buffer.alloc(9); 33 | buf[0] = 0xff; 34 | buf.writeBigUInt64LE(BigInt(i), 1); 35 | return buf; 36 | } 37 | // too large 38 | else { 39 | throw new Error(`Integer too large ${i}`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/bufio/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bigToBufBE'; 2 | export * from './bigToBufLE'; 3 | export * from './bigFromBufBE'; 4 | export * from './bigFromBufLE'; 5 | export * from './bufToStream'; 6 | export * from './encodeVarInt'; 7 | export * from './BufferReader'; 8 | export * from './BufferWriter'; 9 | export * from './Hex'; 10 | export * from './StreamReader'; 11 | export * from './varIntBytes'; 12 | -------------------------------------------------------------------------------- /packages/bufio/lib/varIntBytes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the number of bytes for a VarInt 3 | * @param val 4 | */ 5 | export function varIntBytes(val: number | BigInt) { 6 | if (val < 0) throw new Error('Invalid varint'); 7 | if (val < 0xfd) return 1; 8 | if (val <= 0xffff) return 3; 9 | if (val <= 0xffffffff) return 5; 10 | if (val <= 0xffffffffffffffff) return 9; 11 | throw new Error('Invalid varint'); 12 | } 13 | -------------------------------------------------------------------------------- /packages/bufio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/bufio", 3 | "version": "0.24.1", 4 | "description": "Buffer IO utilities ", 5 | "keywords": [], 6 | "author": "Brian Mancini ", 7 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/bufio", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 12 | }, 13 | "main": "dist/index.js", 14 | "scripts": { 15 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 16 | "lint": "../../node_modules/.bin/eslint lib --ext .ts,.js", 17 | "lint:fix": "../../node_modules/.bin/eslint lib --ext .ts,.js --fix", 18 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 19 | "prepublish": "npm run build" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/bufio/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/bufio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/chainmon/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/chainmon 2 | 3 | ![Build Status](https://github.com/AtomicFinance/node-dlc/actions/workflows/main.yml/badge.svg) 4 | [![Standard Code Style](https://img.shields.io/badge/codestyle-standard-brightgreen.svg)](https://github.com/standard/standard) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](../../LICENSE) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i @node-dlc/chainmon 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/chainmon/__tests__/chainmon.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | describe('chainmon', () => { 4 | it('should', async () => { 5 | expect(1).to.equal(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/chainmon/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.spec.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/chainmon/lib/BlockDiffResult.ts: -------------------------------------------------------------------------------- 1 | import { BlockHeader } from '@node-dlc/bitcoind'; 2 | 3 | export class BlockDiffResult { 4 | constructor( 5 | readonly commonAncestor: BlockHeader, 6 | readonly disconnects: BlockHeader[], 7 | readonly connects: BlockHeader[], 8 | ) {} 9 | } 10 | -------------------------------------------------------------------------------- /packages/chainmon/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TxWatcher'; 2 | export * from './BlockWatcher'; 3 | export * from './BlockDiffResult'; 4 | export * from './BlockDiffer'; 5 | -------------------------------------------------------------------------------- /packages/chainmon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/chainmon", 3 | "version": "0.24.1", 4 | "description": "Bitcoin on-chain transaction monitoring tool for DLCs", 5 | "scripts": { 6 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 7 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 8 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 10 | "prepublish": "npm run build" 11 | }, 12 | "keywords": [ 13 | "dlc", 14 | "monitoring", 15 | "on-chain", 16 | "tool" 17 | ], 18 | "author": "Atomic Finance ", 19 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/chainmon", 20 | "license": "MIT", 21 | "main": "dist/index.js", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 25 | }, 26 | "dependencies": { 27 | "@node-dlc/bitcoin": "^0.24.1", 28 | "@node-dlc/bitcoind": "^0.24.1", 29 | "@node-dlc/logger": "^0.24.1", 30 | "bitcoinjs-lib": "6.1.7" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "18.11.9" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/chainmon/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | }, 6 | "include": ["./lib"], 7 | } 8 | -------------------------------------------------------------------------------- /packages/checksum/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/checksum/README.md: -------------------------------------------------------------------------------- 1 | # `@node-dlc/checksum` 2 | 3 | Calculates a CRC32C checksum based on RFC3720. 4 | 5 | ## Usage 6 | 7 | ```typescript 8 | const { crc32c } = require('@node-dlc/checksum'); 9 | const checksum = crc32c(Buffer.from("hello")); // 2591144780 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/checksum/__tests__/crc32c.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { crc32c } from '../lib/crc32c'; 3 | 4 | describe('crc32c', () => { 5 | const tests: Array<[string, number]> = [ 6 | ['', 0], 7 | [ 8 | '0000000000000000000000000000000000000000000000000000000000000000', 9 | 0x8a9136aa, 10 | ], 11 | [ 12 | 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 13 | 0x62a8ab43, 14 | ], 15 | [ 16 | '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', 17 | 0x46dd794e, 18 | ], 19 | [ 20 | '1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100', 21 | 0x113fdb5c, 22 | ], 23 | ["01c000000000000000000000000000001400000000000400000000140000001828000000000000000200000000000000", 0xd9963a56], // prettier-ignore 24 | ]; 25 | for (const test of tests) { 26 | it(`${test[0]} => ${test[1]}`, () => { 27 | expect(crc32c(Buffer.from(test[0], 'hex'))).to.equal(test[1]); 28 | }); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /packages/checksum/checksum: -------------------------------------------------------------------------------- 1 | ../../../checksum -------------------------------------------------------------------------------- /packages/checksum/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crc32c'; 2 | -------------------------------------------------------------------------------- /packages/checksum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/checksum", 3 | "version": "0.24.1", 4 | "description": "Lightning Network checksum utilities", 5 | "keywords": [], 6 | "author": "Brian Mancini ", 7 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/checksum", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 12 | }, 13 | "main": "dist/index.js", 14 | "scripts": { 15 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 16 | "lint": "../../node_modules/.bin/eslint lib --ext .ts,.js", 17 | "lint:fix": "../../node_modules/.bin/eslint lib --ext .ts,.js --fix", 18 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 19 | "prepublish": "npm run build" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/checksum/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/checksum/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/common/__tests__/bitfield.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { BitField } from '../lib/BitField'; 3 | 4 | describe('BitField', () => { 5 | it('should export BitField', () => { 6 | expect(BitField).to.exist; 7 | }); 8 | 9 | it('should allow creation of BitField', () => { 10 | const bf = new BitField(); 11 | expect(bf).to.be.instanceOf(BitField); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/common/lib/BigIntUtils.ts: -------------------------------------------------------------------------------- 1 | export function calcBytes(num: bigint) { 2 | let b = 0; 3 | while (num > BigInt(0)) { 4 | b += 1; 5 | num /= BigInt(2 ** 8); 6 | } 7 | return b; 8 | } 9 | 10 | export function bigintToBuffer(num: bigint): Buffer { 11 | const bytes = calcBytes(num); 12 | return Buffer.from(num.toString(16).padStart(bytes * 2, '0'), 'hex'); 13 | } 14 | -------------------------------------------------------------------------------- /packages/common/lib/ChannelId.ts: -------------------------------------------------------------------------------- 1 | import { HashByteOrder, OutPoint } from '@node-dlc/bitcoin'; 2 | 3 | /** 4 | * The `channel_id`, defined in [BOLT #2](https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#definition-of-channel_id), 5 | * is a 32-byte identifer for a channel that is based on the channel's 6 | * outpoint. This identifier is used by some messages to identify the 7 | * channel instead of the `short_channel_id`. The `channel_id` is 8 | * derived from the XOR of the big-endiant representation of the 9 | * `funding_txid` and the `funding_output_index`. 10 | */ 11 | export class ChannelId { 12 | /** 13 | * Constructs a `channel_id` from an outpoint by performing an XOR 14 | * of the output index against the last-two bytes of the bid-endian 15 | * txid. 16 | * @param outpoint 17 | * @returns 18 | */ 19 | public static fromOutPoint(outpoint: OutPoint): ChannelId { 20 | if (outpoint.outputIndex > 0xffff) { 21 | throw new Error('Invalid channel_id outpoint'); 22 | } 23 | 24 | const value = outpoint.txid.serialize(HashByteOrder.RPC); 25 | value[30] ^= outpoint.outputIndex >> 8; 26 | value[31] ^= outpoint.outputIndex & 0xff; 27 | return new ChannelId(value); 28 | } 29 | 30 | constructor(readonly value: Buffer) {} 31 | 32 | /** 33 | * Returns true if the `channel_id`s are equal. 34 | * @param other 35 | * @returns 36 | */ 37 | public equals(other: ChannelId): boolean { 38 | return this.value.equals(other.value); 39 | } 40 | 41 | /** 42 | * Serializes to the hex representation of the `channel_id` 43 | */ 44 | public toString(): string { 45 | return this.value.toString('hex'); 46 | } 47 | 48 | /** 49 | * Serializes to a buffer 50 | * @returns 51 | */ 52 | public toBuffer(): Buffer { 53 | return Buffer.from(this.value); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/common/lib/ShortChannelId.ts: -------------------------------------------------------------------------------- 1 | import { shortChannelIdToNumber } from './ShortChannelIdUtils'; 2 | import { shortChannelIdToBuffer } from './ShortChannelIdUtils'; 3 | import { shortChannelIdToString } from './ShortChannelIdUtils'; 4 | 5 | export class ShortChannelId { 6 | /** 7 | * Block height 8 | */ 9 | public block: number; 10 | 11 | /** 12 | * Transaction index in the block 13 | */ 14 | public txIdx: number; 15 | 16 | /** 17 | * Output index in transaction 18 | */ 19 | public voutIdx: number; 20 | 21 | /** 22 | * Defined in Lightning BOLT 07. This object represents a fast way to look 23 | * up the funding transaction for a channel and consists of the block, the 24 | * index of a transaction in the block, and the index of an output in that 25 | * transaction. 26 | * 27 | * This object can take on several forms. 28 | */ 29 | constructor(block: number, txIdx: number, voutIdx: number) { 30 | this.block = block; 31 | this.txIdx = txIdx; 32 | this.voutIdx = voutIdx; 33 | } 34 | 35 | /** 36 | * Converts the short_channel_id into a number 37 | */ 38 | public toNumber(): bigint { 39 | return shortChannelIdToNumber(this); 40 | } 41 | 42 | /** 43 | * Converts the short_channel_id into a human 44 | * readable string in the format [block]x[txidx]x[voutidx] 45 | */ 46 | public toString(): string { 47 | return shortChannelIdToString(this); 48 | } 49 | 50 | /** 51 | * Converts the short_chanenl_id into a buffer in the expected 52 | * serialization format. 53 | */ 54 | public toBuffer(): Buffer { 55 | return shortChannelIdToBuffer(this); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/common/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Base32'; 2 | export * from './BigIntUtils'; 3 | export * from './BitField'; 4 | export * from './ChannelId'; 5 | export * from './ShortChannelId'; 6 | export * from './ShortChannelIdUtils'; 7 | -------------------------------------------------------------------------------- /packages/common/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/common", 3 | "version": "0.24.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@node-dlc/common", 9 | "version": "0.24.1", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "18.11.9" 13 | } 14 | }, 15 | "node_modules/@types/node": { 16 | "version": "18.11.9", 17 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", 18 | "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", 19 | "dev": true, 20 | "license": "MIT" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/common", 3 | "version": "0.24.1", 4 | "description": "Common utilities and types for node-dlc", 5 | "keywords": [ 6 | "dlc", 7 | "bitcoin", 8 | "lightning", 9 | "utilities" 10 | ], 11 | "author": "Atomic Finance ", 12 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/common", 13 | "license": "MIT", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 17 | }, 18 | "main": "dist/index.js", 19 | "scripts": { 20 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 21 | "lint": "../../node_modules/.bin/eslint lib --ext .ts,.js", 22 | "lint:fix": "../../node_modules/.bin/eslint lib --ext .ts,.js --fix", 23 | "build": "../../node_modules/.bin/tsc --project ./tsconfig.json", 24 | "prepublish": "npm run build" 25 | }, 26 | "dependencies": { 27 | "@node-dlc/bitcoin": "^0.24.1", 28 | "@node-dlc/bufio": "^0.24.1" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "18.11.9" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./lib" 6 | }, 7 | "include": ["./lib"] 8 | } -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/core 2 | 3 | ![Build Status](https://github.com/AtomicFinance/node-dlc/actions/workflows/main.yml/badge.svg) 4 | [![Standard Code Style](https://img.shields.io/badge/codestyle-standard-brightgreen.svg)](https://github.com/standard/standard) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](../../LICENSE) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i @node-dlc/core 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/core/__tests__/core.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | describe('core', () => { 4 | it('should', () => { 5 | expect(1).to.equal(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/core/__tests__/dlc/finance/CoveredCall.spec.ts: -------------------------------------------------------------------------------- 1 | import { HyperbolaPayoutCurvePiece } from '@node-dlc/messaging'; 2 | import { expect } from 'chai'; 3 | 4 | import { CoveredCall } from '../../../lib/dlc/finance/CoveredCall'; 5 | import { HyperbolaPayoutCurve } from '../../../lib/dlc/HyperbolaPayoutCurve'; 6 | 7 | describe('CoveredCall', () => { 8 | describe('1BTC-50k-base2-20digit curve', () => { 9 | const strikePrice = BigInt(50000); 10 | const contractSize = BigInt(10) ** BigInt(8); 11 | const oracleBase = 2; 12 | const oracleDigits = 20; 13 | 14 | const { maxOutcome, totalCollateral, payoutCurve } = CoveredCall.buildCurve( 15 | strikePrice, 16 | contractSize, 17 | oracleBase, 18 | oracleDigits, 19 | ); 20 | 21 | describe('payout', () => { 22 | it('should be a negative past max outcome', () => { 23 | expect( 24 | payoutCurve.getPayout(maxOutcome + BigInt(1)).toNumber(), 25 | ).to.be.lessThan(0); 26 | }); 27 | 28 | it('should be equal to totalCollateral at strike price', () => { 29 | expect(payoutCurve.getPayout(strikePrice).toNumber()).to.equal( 30 | Number(totalCollateral), 31 | ); 32 | }); 33 | }); 34 | 35 | it('should serialize and deserialize properly', () => { 36 | const payout = payoutCurve.getPayout(strikePrice); 37 | 38 | const _tlv = payoutCurve.toPayoutCurvePiece().serialize(); 39 | const pf = HyperbolaPayoutCurvePiece.deserialize(_tlv); 40 | 41 | const deserializedCurve = HyperbolaPayoutCurve.fromPayoutCurvePiece(pf); 42 | 43 | expect(payout.toNumber()).to.be.eq( 44 | deserializedCurve.getPayout(strikePrice).toNumber(), 45 | ); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/core/__tests__/dlc/finance/LongCall.spec.ts: -------------------------------------------------------------------------------- 1 | import { HyperbolaPayoutCurvePiece } from '@node-dlc/messaging'; 2 | import { expect } from 'chai'; 3 | 4 | import { LongCall } from '../../../lib/dlc/finance/LongCall'; 5 | import { HyperbolaPayoutCurve } from '../../../lib/dlc/HyperbolaPayoutCurve'; 6 | 7 | describe('LongCall', () => { 8 | describe('1BTC-50k-base2-18digit curve', () => { 9 | const strikePrice = BigInt(50000); 10 | const contractSize = BigInt(10) ** BigInt(8); 11 | const oracleBase = 2; 12 | const oracleDigits = 18; 13 | 14 | const { payoutCurve } = LongCall.buildCurve( 15 | strikePrice, 16 | contractSize, 17 | oracleBase, 18 | oracleDigits, 19 | ); 20 | 21 | describe('payout', () => { 22 | const priceIncrease = BigInt(1000); 23 | 24 | it('should be positive as the position goes ITM', () => { 25 | expect( 26 | payoutCurve 27 | .getPayout(strikePrice + priceIncrease) 28 | .integerValue() 29 | .toNumber(), 30 | ).to.equal( 31 | Number( 32 | (contractSize * priceIncrease) / (strikePrice + priceIncrease), 33 | ), 34 | ); 35 | }); 36 | 37 | it('should be zero at strike price', () => { 38 | expect(payoutCurve.getPayout(strikePrice).toNumber()).to.equal(0); 39 | }); 40 | }); 41 | 42 | it('should serialize and deserialize properly', () => { 43 | const payout = payoutCurve.getPayout(strikePrice); 44 | 45 | const _tlv = payoutCurve.toPayoutCurvePiece().serialize(); 46 | const pf = HyperbolaPayoutCurvePiece.deserialize(_tlv); 47 | 48 | const deserializedCurve = HyperbolaPayoutCurve.fromPayoutCurvePiece(pf); 49 | 50 | expect(payout.toNumber()).to.be.eq( 51 | deserializedCurve.getPayout(strikePrice).toNumber(), 52 | ); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/core/__tests__/dlc/finance/LongPut.spec.ts: -------------------------------------------------------------------------------- 1 | import { HyperbolaPayoutCurvePiece } from '@node-dlc/messaging'; 2 | import { expect } from 'chai'; 3 | 4 | import { LongPut } from '../../../lib'; 5 | import { HyperbolaPayoutCurve } from '../../../lib/dlc/HyperbolaPayoutCurve'; 6 | 7 | describe('LongPut', () => { 8 | describe('1BTC-50k-base2-18digit curve', () => { 9 | const strikePrice = BigInt(50000); 10 | const contractSize = BigInt(10) ** BigInt(8); 11 | const oracleBase = 2; 12 | const oracleDigits = 18; 13 | 14 | const { payoutCurve } = LongPut.buildCurve( 15 | strikePrice, 16 | contractSize, 17 | oracleBase, 18 | oracleDigits, 19 | ); 20 | 21 | describe('payout', () => { 22 | const priceDecrease = BigInt(1000); 23 | 24 | it('should be positive as the position goes ITM', () => { 25 | expect( 26 | payoutCurve 27 | .getPayout(strikePrice - priceDecrease) 28 | .integerValue() 29 | .toNumber(), 30 | ).to.equal( 31 | Number( 32 | (contractSize * priceDecrease) / (strikePrice - priceDecrease), 33 | ), 34 | ); 35 | }); 36 | 37 | it('should be zero at strike price', () => { 38 | expect(payoutCurve.getPayout(strikePrice).toNumber()).to.equal(0); 39 | }); 40 | }); 41 | 42 | it('should serialize and deserialize properly', () => { 43 | const payout = payoutCurve.getPayout(strikePrice); 44 | 45 | const _tlv = payoutCurve.toPayoutCurvePiece().serialize(); 46 | const pf = HyperbolaPayoutCurvePiece.deserialize(_tlv); 47 | 48 | const deserializedCurve = HyperbolaPayoutCurve.fromPayoutCurvePiece(pf); 49 | 50 | expect(payout.toNumber()).to.be.eq( 51 | deserializedCurve.getPayout(strikePrice).toNumber(), 52 | ); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/core/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.spec.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/__tests__/utils/precision.spec.ts: -------------------------------------------------------------------------------- 1 | // write tests for getPrecision and fromPrecision 2 | 3 | import BigNumber from 'bignumber.js'; 4 | import { expect } from 'chai'; 5 | 6 | import { fromPrecision, getPrecision } from '../../lib/utils/Precision'; 7 | 8 | describe('getPrecision', () => { 9 | it('should get correct precision for integers', () => { 10 | const num = new BigNumber(1); 11 | expect(getPrecision(num)).eq(0); 12 | }); 13 | 14 | it('should get correct precision for multiple decimal points', () => { 15 | const num = new BigNumber(1.123456789); 16 | expect(getPrecision(num)).eq(1234567890000000); 17 | }); 18 | 19 | it('should get correct precision for max decimal points', () => { 20 | const num = new BigNumber(1.112233445566778899); 21 | expect(getPrecision(num)).eq(1122334455667788); 22 | }); 23 | }); 24 | 25 | describe('fromPrecision', () => { 26 | it('should create correct number from precision', () => { 27 | const num = 12345; 28 | expect(fromPrecision(num).toFormat()).eq('0.0000000000012345'); 29 | }); 30 | 31 | it('should create correct number from precision for max precision', () => { 32 | const num = 1122334455667788; 33 | expect(fromPrecision(num).toFormat()).eq('0.1122334455667788'); 34 | }); 35 | 36 | it('should throw error if precision is too large', () => { 37 | const num = 11223344556677889; 38 | expect(() => fromPrecision(num)).to.throw(Error); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/core/lib/AsyncProcessingQueue.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | import { Queue } from './Queue'; 4 | 5 | /** 6 | * Serializes execution of events. This class is constructed with 7 | * a delegate function that is executed for each item that is enqueued. 8 | */ 9 | export class AsyncProcessingQueue extends EventEmitter { 10 | private _fn: (...args: T[]) => Promise; 11 | private _queue: Queue; 12 | private _flushHandle: any; 13 | 14 | constructor(fn: (...args: T[]) => Promise) { 15 | super(); 16 | this._fn = fn; 17 | this._queue = new Queue(); 18 | this._flush = this._flush.bind(this); 19 | } 20 | 21 | /** 22 | * Adds a new item to the processing queue 23 | */ 24 | public enqueue(value: T) { 25 | this._queue.enqueue(value); 26 | 27 | // Postpone flushing until end of event loop to allow multiple operations 28 | // to enqueue. This handle will be cleared once flushing has completed. 29 | if (!this._flushHandle) this._flushHandle = setImmediate(this._flush); 30 | } 31 | 32 | /** 33 | * Gets the number of pending items in the processor queue 34 | * @type {number} 35 | */ 36 | get size(): number { 37 | return this._queue.length; 38 | } 39 | 40 | private async _flush() { 41 | // emit that flushing is starting 42 | this.emit('flushing'); 43 | 44 | // process all items on the queue, even items that 45 | // are added to the queue while flushing is occuring 46 | while (this._queue.length > 0) { 47 | try { 48 | const value = this._queue.dequeue(); 49 | await this._fn(value); 50 | } catch (ex) { 51 | this.emit('error', ex); 52 | } 53 | } 54 | 55 | // emit flushing has completed 56 | this.emit('flushed'); 57 | 58 | // clear flush handle so that next enqueue will trigger a flush 59 | this._flushHandle = undefined; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/lib/Base32.ts: -------------------------------------------------------------------------------- 1 | // Re-export Base32 from common package to maintain backward compatibility 2 | export { Base32 } from '@node-dlc/common'; 3 | -------------------------------------------------------------------------------- /packages/core/lib/BigIntUtils.ts: -------------------------------------------------------------------------------- 1 | // Re-export BigIntUtils functions from common package to maintain backward compatibility 2 | export { calcBytes, bigintToBuffer } from '@node-dlc/common'; 3 | -------------------------------------------------------------------------------- /packages/core/lib/BitField.ts: -------------------------------------------------------------------------------- 1 | // Re-export BitField from common package to maintain backward compatibility 2 | export { BitField } from '@node-dlc/common'; 3 | -------------------------------------------------------------------------------- /packages/core/lib/ChannelId.ts: -------------------------------------------------------------------------------- 1 | // Re-export ChannelId from common package to maintain backward compatibility 2 | export { ChannelId } from '@node-dlc/common'; 3 | -------------------------------------------------------------------------------- /packages/core/lib/LinkedListNode.ts: -------------------------------------------------------------------------------- 1 | export class LinkedListNode { 2 | public value: T; 3 | public prev: LinkedListNode; 4 | public next: LinkedListNode; 5 | 6 | /** 7 | * Creates a linked list node with the specified data 8 | */ 9 | constructor(value: T) { 10 | this.value = value; 11 | this.prev = null; 12 | this.next = null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/lib/Queue.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from './LinkedList'; 2 | 3 | /** 4 | * FIFO queue implemented with O(1) enqueue and dequeue operations 5 | */ 6 | export class Queue { 7 | private _list: LinkedList = new LinkedList(); 8 | 9 | /** 10 | * Peak the tip value 11 | */ 12 | public peak(): T { 13 | return this._list.head && this._list.head.value; 14 | } 15 | 16 | /** 17 | * Returns the length of the queue 18 | */ 19 | get length(): number { 20 | return this._list.length; 21 | } 22 | 23 | /** 24 | * Enqueue a value 25 | */ 26 | public enqueue(value: T) { 27 | this._list.add(value); 28 | } 29 | 30 | /** 31 | * Dequeue top most value 32 | */ 33 | public dequeue(): T { 34 | return this._list.remove(0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/lib/ShortChannelId.ts: -------------------------------------------------------------------------------- 1 | // Re-export ShortChannelId from common package to maintain backward compatibility 2 | export { ShortChannelId } from '@node-dlc/common'; 3 | -------------------------------------------------------------------------------- /packages/core/lib/ShortChannelIdUtils.ts: -------------------------------------------------------------------------------- 1 | // Re-export ShortChannelId utilities from common package to maintain backward compatibility 2 | export { 3 | shortChannelIdToBuffer, 4 | shortChannelIdToNumber, 5 | shortChannelIdFromString, 6 | shortChannelIdFromBuffer, 7 | shortChannelIdFromNumber, 8 | shortChannelIdToString, 9 | } from '@node-dlc/common'; 10 | -------------------------------------------------------------------------------- /packages/core/lib/dlc/PayoutCurve.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export default interface PayoutCurve { 4 | getPayout(x: BigInt): BigNumber; 5 | getOutcomeForPayout(y: BigNumber): BigInt; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/lib/lightning/ChannelId.ts: -------------------------------------------------------------------------------- 1 | import { HashByteOrder, OutPoint } from '@node-dlc/bitcoin'; 2 | 3 | /** 4 | * ChannelId type that that encapsulates an outpoint as a 32-byte value 5 | * and is used to identify a channel. This type is defined in BOLT 2 6 | * under the peer protocol. It is defined as combinging the funding_txid 7 | * and the funding_output_index, using big-endian XOR (meaning the 8 | * output_index modifies the last two bytes). 9 | */ 10 | export class ChannelId { 11 | public static fromOutPoint(outpoint: OutPoint): ChannelId { 12 | const txid = outpoint.txid.serialize(HashByteOrder.RPC); 13 | 14 | if (outpoint.outputIndex > 0xffff) { 15 | throw new Error('Invalid output index length'); 16 | } 17 | 18 | txid[30] ^= outpoint.outputIndex >> 8; 19 | txid[31] ^= outpoint.outputIndex & 0xff; 20 | 21 | return new ChannelId(txid); 22 | } 23 | 24 | constructor(readonly value: Buffer) {} 25 | 26 | /** 27 | * Converts the ChannelId into a buffer 28 | */ 29 | public toBuffer(): Buffer { 30 | return Buffer.from(this.value); 31 | } 32 | 33 | /** 34 | * Converts the ChannelId into a hex-encoded string 35 | */ 36 | public toString(): string { 37 | return this.toHex(); 38 | } 39 | 40 | /** 41 | * Converts the ChannelId to a hex-encoded string 42 | */ 43 | public toHex(): string { 44 | return this.value.toString('hex'); 45 | } 46 | 47 | /** 48 | * Returns true if the ChannelIds are byte-wise equal 49 | * @param other 50 | */ 51 | public equals(other: ChannelId): boolean { 52 | return this.value.equals(other.value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/core/lib/lightning/CommitmentSecret.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@node-dlc/crypto'; 2 | 3 | export class CommitmentSecret { 4 | /** 5 | * Generic version of commitment secret generator as defined in 6 | * BOLT3. 7 | * 8 | * This function works by decrementing from bits to 0. If the index 9 | * number has the corresponding bit set, it flips the bit in the 10 | * secret and hashes it. 11 | * 12 | * The natural conclusion is the first secret flips every bit and 13 | * the last secret flips zero (and is thus the seed). 14 | * 15 | * This function can be used for generate the local per-commitment 16 | * secret based on the per-commitment seed and a commitment index 17 | * (in this case it) generates all 48 bits. 18 | * 19 | * This function can also be used to generate prior commitment 20 | * secrets from a newer secret, which acts as a prefix. 21 | * 22 | * @param base base secret that will be used to derive from 23 | * @param i secret at index I to generate 24 | * @param bits bits to evaluate, default is 48 25 | */ 26 | public static derive(base: Buffer, i: bigint, bits = 48): Buffer { 27 | // intii 28 | let p = Buffer.from(base); 29 | 30 | for (let b = bits - 1; b >= 0; b--) { 31 | if (i & (BigInt(1) << BigInt(b))) { 32 | // flip the specified bit in the specific byte 33 | const byteIndex = Math.floor(b / 8); 34 | const bitIndex = b % 8; 35 | p[byteIndex] = p[byteIndex] ^ (1 << bitIndex); 36 | p = sha256(p); 37 | } 38 | } 39 | return p; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/lib/lightning/HtlcDirection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Direction, from our node's perspective, of an HTLC. 3 | */ 4 | export enum HtlcDirection { 5 | /** 6 | * We have offered this HTLC to pay a node directly or on behalf 7 | * of a forward request. 8 | */ 9 | Offered = 0, 10 | 11 | /** 12 | * We have accepted this HTLC from the counterparty. We may be the 13 | * final hop in a chain (and would posssess) the preimage or we may 14 | * be a processing hop in a sequence of payments. 15 | */ 16 | Accepted = 1, 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/lib/utils/BigIntUtils.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export class BigIntMath { 4 | static max(...values: bigint[]): bigint { 5 | if (values.length === 0) { 6 | return null; 7 | } 8 | 9 | if (values.length === 1) { 10 | return values[0]; 11 | } 12 | 13 | let max = values[0]; 14 | for (let i = 1; i < values.length; i++) { 15 | if (values[i] > max) { 16 | max = values[i]; 17 | } 18 | } 19 | return max; 20 | } 21 | 22 | static min(...values: bigint[]): bigint { 23 | if (values.length === 0) { 24 | return null; 25 | } 26 | 27 | if (values.length === 1) { 28 | return values[0]; 29 | } 30 | 31 | let min = values[0]; 32 | for (let i = 1; i < values.length; i++) { 33 | if (values[i] < min) { 34 | min = values[i]; 35 | } 36 | } 37 | return min; 38 | } 39 | 40 | static sign(value: bigint): bigint { 41 | if (value > BigInt(0)) { 42 | return BigInt(1); 43 | } 44 | if (value < BigInt(0)) { 45 | return BigInt(-1); 46 | } 47 | return BigInt(0); 48 | } 49 | 50 | static abs(value: bigint): bigint { 51 | if (this.sign(value) === BigInt(-1)) { 52 | return -value; 53 | } 54 | return value; 55 | } 56 | 57 | static clamp(min: bigint, val: bigint, max: bigint): bigint { 58 | return this.max(min, this.min(val, max)); 59 | } 60 | } 61 | 62 | export function toBigInt(num: BigNumber): bigint { 63 | return BigInt(num.integerValue().toString()); 64 | } 65 | -------------------------------------------------------------------------------- /packages/core/lib/utils/Precision.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | // Precision refers to the number of digits after the decimal point. 4 | // Refer to https://github.com/discreetlogcontracts/dlcspecs/blob/master/PayoutCurve.md#version-0-payout_function 5 | 6 | /** 7 | * Extract the precision of a number 8 | * @param num 9 | * @returns The precision of the number 10 | */ 11 | export const getPrecision = (num: BigNumber): number => 12 | num.decimalPlaces(16).abs().modulo(1).shiftedBy(16).toNumber(); 13 | 14 | /** 15 | * Creates a precise number from a precision 16 | * @param precision 17 | * @returns The number with the given precision 18 | */ 19 | export const fromPrecision = (precision: number): BigNumber => { 20 | if (precision.toString().length > 16) 21 | throw new Error('Precision is too large'); 22 | return new BigNumber(precision).shiftedBy(-16); 23 | }; 24 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/core", 3 | "version": "0.24.1", 4 | "description": "DLC Core", 5 | "scripts": { 6 | "start": "npm run build && node dist/index.js", 7 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 8 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 10 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 11 | "prepublish": "npm run build" 12 | }, 13 | "keywords": [ 14 | "dlc", 15 | "core" 16 | ], 17 | "author": "Atomic Finance ", 18 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/daemon", 19 | "license": "MIT", 20 | "main": "dist/index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 24 | }, 25 | "dependencies": { 26 | "@node-dlc/bitcoin": "^0.24.1", 27 | "@node-dlc/bufio": "^0.24.1", 28 | "@node-dlc/common": "^0.24.1", 29 | "@node-dlc/crypto": "^0.24.1", 30 | "bignumber.js": "^9.0.1", 31 | "bitcoin-networks": "^1.0.0", 32 | "decimal.js": "10.4.3" 33 | }, 34 | "devDependencies": { 35 | "@node-dlc/messaging": "^0.24.1", 36 | "@types/node": "18.11.9" 37 | }, 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | }, 6 | "include": ["./lib"], 7 | "exclude": ["./dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/crypto/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/crypto/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/crypto 2 | 3 | This packages uses the native Node.js crypto for all functionality except secp256k1. This package requires Node.js 10.17+. 4 | 5 | This package provides cryptographic functions for use in LN Tools: 6 | 7 | - sha256 8 | - hash160 9 | - hkdf 10 | - secp256k1 ECDH 11 | - secp256k1 private key generation 12 | - secp256k1 public key derivation 13 | - chacha20-poly1305 AEAD 14 | -------------------------------------------------------------------------------- /packages/crypto/__tests__/chacha.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { chachaDecrypt, chachaEncrypt } from "../lib/chacha"; 3 | 4 | describe("chachaEncrypt", () => { 5 | it("encrypts", () => { 6 | const key = Buffer.alloc(32); 7 | const iv = Buffer.alloc(16); 8 | const data = Buffer.alloc(64); 9 | const result = chachaEncrypt(key, iv, data); 10 | expect(result.toString("hex")).to.equal( 11 | "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", 12 | ); 13 | }); 14 | }); 15 | 16 | describe("chachaDecrypt", () => { 17 | it("decrypts", () => { 18 | const key = Buffer.alloc(32); 19 | const iv = Buffer.alloc(16); 20 | const cipher = Buffer.from( 21 | "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", 22 | "hex", 23 | ); 24 | const result = chachaDecrypt(key, iv, cipher); 25 | expect(result.toString("hex")).to.equal( 26 | "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/crypto/__tests__/xor.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { xor } from "../lib/xor"; 3 | 4 | describe("xor", () => { 5 | it("equal length", () => { 6 | const a = Buffer.from("0101", "hex"); 7 | const b = Buffer.from("0101", "hex"); 8 | expect(xor(a, b).toString("hex")).to.equal("0000"); 9 | }); 10 | 11 | it("equal length", () => { 12 | const a = Buffer.from("ffff", "hex"); 13 | const b = Buffer.from("0000", "hex"); 14 | expect(xor(a, b).toString("hex")).to.equal("ffff"); 15 | }); 16 | 17 | it("shorter a", () => { 18 | const a = Buffer.from("ff", "hex"); 19 | const b = Buffer.from("0000", "hex"); 20 | expect(xor(a, b).toString("hex")).to.equal("ff"); 21 | }); 22 | 23 | it("shorter b", () => { 24 | const a = Buffer.from("ffff", "hex"); 25 | const b = Buffer.from("00", "hex"); 26 | expect(xor(a, b).toString("hex")).to.equal("ff"); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/crypto/crypto: -------------------------------------------------------------------------------- 1 | ../../../crypto -------------------------------------------------------------------------------- /packages/crypto/lib/aes-key.ts: -------------------------------------------------------------------------------- 1 | export type AesKey = { 2 | salt: Buffer; 3 | key: Buffer; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/crypto/lib/chacha.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto'; 2 | 3 | export function chachaEncrypt(key: Buffer, iv: Buffer, data: Buffer): Buffer { 4 | const cipher = crypto.createCipheriv('chacha20' as any, key, iv); 5 | return cipher.update(data); 6 | } 7 | 8 | export function chachaDecrypt(key: Buffer, iv: Buffer, cipher: Buffer): Buffer { 9 | const decipher = crypto.createDecipheriv('chacha20' as any, key, iv); 10 | return decipher.update(cipher); 11 | } 12 | -------------------------------------------------------------------------------- /packages/crypto/lib/chachapoly.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | /** 4 | * Encrypt data using authenticated encryption with associated data (AEAD) 5 | * ChaCha20-Poly1305. 6 | * 7 | * @param k private key, 64-bytes 8 | * @param n nonce, 12-bytes 9 | * @param ad associated data 10 | * @param plaintext raw data to encrypt 11 | * @returns encrypted data + tag as a variable length buffer 12 | */ 13 | export function ccpEncrypt( 14 | k: Buffer, 15 | n: Buffer, 16 | ad: Buffer, 17 | plaintext: Buffer, 18 | ): Buffer { 19 | const cipher = crypto.createCipheriv('ChaCha20-Poly1305' as any, k, n, { 20 | authTagLength: 16, 21 | }); 22 | cipher.setAAD(ad, undefined); 23 | const pad = cipher.update(plaintext); 24 | cipher.final(); 25 | const tag = cipher.getAuthTag(); 26 | return Buffer.concat([pad, tag]); 27 | } 28 | 29 | /** 30 | * Decrypt data uusing authenticated encryption with associated data (AEAD) 31 | * ChaCha20-Poly1305 32 | * 33 | * @param k private key, 64-bytes 34 | * @param n nonce, 12-bytes 35 | * @param ad associated data, variable length 36 | * @param ciphertext encrypted data to decrypt 37 | * @returns decrypteed data as a variable length Buffer 38 | */ 39 | export function ccpDecrypt( 40 | k: Buffer, 41 | n: Buffer, 42 | ad: Buffer, 43 | ciphertext: Buffer, 44 | ) { 45 | const decipher = crypto.createDecipheriv('ChaCha20-Poly1305' as any, k, n, { 46 | authTagLength: 16, 47 | }); 48 | decipher.setAAD(ad, undefined); 49 | 50 | if (ciphertext.length === 16) { 51 | decipher.setAuthTag(ciphertext); 52 | return decipher.final(); 53 | } 54 | if (ciphertext.length > 16) { 55 | const tag = ciphertext.slice(ciphertext.length - 16); 56 | const pad = ciphertext.slice(0, ciphertext.length - 16); 57 | decipher.setAuthTag(tag); 58 | let m = decipher.update(pad); 59 | const f = decipher.final(); 60 | m = Buffer.concat([m, f]); 61 | return m; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/crypto/lib/hash.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | /** 4 | * Hash using SHA256 5 | * 6 | * @param data data to hash 7 | * @returns 32-byte digest 8 | */ 9 | export function sha256(data: Buffer): Buffer { 10 | const hash = crypto.createHash('sha256'); 11 | hash.update(data); 12 | return hash.digest(); 13 | } 14 | 15 | /** 16 | * Hash using RIPEM160 17 | * @param data data to hash, any length 18 | * @returns 20-byte digest 19 | */ 20 | export function ripemd160(data: Buffer): Buffer { 21 | const hash = crypto.createHash('ripemd160'); 22 | hash.update(data); 23 | return hash.digest(); 24 | } 25 | 26 | /** 27 | * Hash using ripmd160 and sha256 28 | * 29 | * @param data data to hash, any length 30 | * @returns 20-byte digest 31 | */ 32 | export function hash160(data: Buffer): Buffer { 33 | return ripemd160(sha256(data)); 34 | } 35 | 36 | /** 37 | * Performs double sha256 hash 38 | * 39 | * @param data data to hash, any length 40 | * @returns 32-byte digest 41 | */ 42 | export function hash256(data: Buffer): Buffer { 43 | return sha256(sha256(data)); 44 | } 45 | -------------------------------------------------------------------------------- /packages/crypto/lib/hkdf.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | /** 4 | * HMAC-based Extact and Expand Key Derivation Function (HKDF) 5 | * that complies with RFC 5869. 6 | * 7 | * @remarks 8 | * Refer to https://tools.ietf.org/html/rfc5869 for detailed information. 9 | * 10 | * The current implementation only supports up to 254 expansion rounds for 11 | * a total of 255 rounds. 12 | * 13 | * @param ikm initial key material, variable length 14 | * @param len output key material length 15 | * @param salt optional, defaults to none 16 | * @param info optional, defaults to none 17 | * @param hash optional, defaults to sha256 18 | * @returns output key material of specified length 19 | */ 20 | export function hkdf( 21 | ikm: Buffer, 22 | len: number, 23 | salt: Buffer = Buffer.alloc(0), 24 | info: Buffer = Buffer.alloc(0), 25 | hash = 'sha256', 26 | ) { 27 | // extract step 28 | const prk = hmacHash(salt, ikm, hash); 29 | 30 | // expand 31 | const n = Math.ceil(len / prk.byteLength); 32 | 33 | // validate we don't overflow iteration counter buffer. 34 | // Note that we can support larger values but will need 35 | // to modify how the integer byte is constructed, additionally 36 | // the specification does not specify the endianness of the counter 37 | if (n > 255) throw new Error('Output length exceeds maximum'); 38 | 39 | const t = [Buffer.alloc(0)]; 40 | for (let i = 1; i <= n; i++) { 41 | const tp = t[t.length - 1]; 42 | const bi = Buffer.from([i]); 43 | t.push(hmacHash(prk, Buffer.concat([tp, info, bi]), hash)); 44 | } 45 | return Buffer.concat(t.slice(1)).slice(0, len); 46 | } 47 | 48 | function hmacHash(key: Buffer, input: Buffer, hash: string) { 49 | const hmac = crypto.createHmac(hash, key); 50 | hmac.update(input); 51 | return hmac.digest(); 52 | } 53 | -------------------------------------------------------------------------------- /packages/crypto/lib/hmac.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | 3 | export function hmac(key: Buffer, data: Buffer, algorithm = 'sha256') { 4 | const h = crypto.createHmac(algorithm, key); 5 | h.update(data); 6 | return h.digest(); 7 | } 8 | -------------------------------------------------------------------------------- /packages/crypto/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './aes'; 2 | export * from './aes-key'; 3 | export * from './chacha'; 4 | export * from './chachapoly'; 5 | export * from './hash'; 6 | export * from './hkdf'; 7 | export * from './hmac'; 8 | export * from './key'; 9 | export * from './secp256k1'; 10 | export * from './xor'; 11 | -------------------------------------------------------------------------------- /packages/crypto/lib/xor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Performs XOR of the two buffers where only the min length will be XOR'd 3 | * @param a 4 | * @param b 5 | */ 6 | export function xor(a: Buffer, b: Buffer) { 7 | const result = Buffer.alloc(Math.min(a.length, b.length)); 8 | for (let i = 0; i < result.length; i++) { 9 | result[i] = a[i] ^ b[i]; 10 | } 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /packages/crypto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/crypto", 3 | "version": "0.24.1", 4 | "description": "Lightning cryptography utilities", 5 | "keywords": [ 6 | "lightning", 7 | "crypto", 8 | "bitcoin" 9 | ], 10 | "author": "Brian Mancini ", 11 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/crypto", 12 | "license": "MIT", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 16 | }, 17 | "main": "dist/index.js", 18 | "scripts": { 19 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 20 | "lint": "../../node_modules/.bin/eslint lib --ext .ts,.js", 21 | "lint:fix": "../../node_modules/.bin/eslint lib --ext .ts,.js --fix", 22 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 23 | "prepublish": "npm run build" 24 | }, 25 | "engines": { 26 | "node": ">=10.17" 27 | }, 28 | "dependencies": { 29 | "secp256k1": "^4.0.2" 30 | }, 31 | "publishConfig": { 32 | "access": "public" 33 | }, 34 | "devDependencies": { 35 | "@types/secp256k1": "^4.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/crypto/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/crypto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/leveldb/README.md: -------------------------------------------------------------------------------- 1 | # `@node-dlc/leveldb` 2 | 3 | LevelDB package for DLC message and transaction storage. 4 | 5 | ## Overview 6 | 7 | This package provides a LevelDB-based storage layer for DLC (Discreet Log Contract) operations. It uses the `classic-level` library to provide a Node.js interface to LevelDB. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install @node-dlc/leveldb 13 | ``` 14 | 15 | ## Usage 16 | 17 | The package provides several store classes for different types of data: 18 | 19 | - `LeveldbDlcStore` - For DLC offers, accepts, signs, cancels, closes, and transactions 20 | - `LeveldbOracleStore` - For oracle events and identifiers 21 | - `LeveldbOrderStore` - For order offers and accepts 22 | - `LeveldbWalletStore` - For wallet seed and address cache storage 23 | - `LeveldbIrcStore` - For IRC-related DLC data 24 | - `LeveldbInfoStore` - For DLC statistics and info 25 | - `LeveldbGossipStore` - For gossip protocol data 26 | 27 | Each store extends the base `LeveldbBase` class which provides common database operations. 28 | -------------------------------------------------------------------------------- /packages/leveldb/__tests__/leveldb.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | import { 5 | LeveldbDlcStore, 6 | LeveldbGossipStore, 7 | LeveldbInfoStore, 8 | LeveldbIrcStore, 9 | LeveldbOracleStore, 10 | LeveldbOrderStore, 11 | LeveldbWalletStore, 12 | } from '../lib'; 13 | 14 | /** 15 | * Recursively deletes a directory 16 | * @param dir path to directory 17 | */ 18 | export function cleanDir(dir: string): void { 19 | if (fs.existsSync(dir)) { 20 | fs.readdirSync(dir).forEach((file) => { 21 | const curPath = path.join(dir, file); 22 | if (fs.lstatSync(curPath).isDirectory()) { 23 | cleanDir(curPath); 24 | } else { 25 | fs.unlinkSync(curPath); 26 | } 27 | }); 28 | fs.rmdirSync(dir); 29 | } 30 | } 31 | 32 | export const dlcStore = new LeveldbDlcStore('./tmp-leveldb'); 33 | export const gossipStore = new LeveldbGossipStore('./tmp-leveldb'); 34 | export const infoStore = new LeveldbInfoStore('./tmp-leveldb'); 35 | export const ircStore = new LeveldbIrcStore('./tmp-leveldb'); 36 | export const oracleStore = new LeveldbOracleStore('./tmp-leveldb'); 37 | export const orderStore = new LeveldbOrderStore('./tmp-leveldb'); 38 | export const walletStore = new LeveldbWalletStore('./tmp-leveldb'); 39 | 40 | // Alias for backward compatibility 41 | export const rmdir = cleanDir; 42 | 43 | export { 44 | LeveldbDlcStore, 45 | LeveldbGossipStore, 46 | LeveldbInfoStore, 47 | LeveldbIrcStore, 48 | LeveldbOracleStore, 49 | LeveldbOrderStore, 50 | LeveldbWalletStore, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/leveldb/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/leveldb/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './leveldb-base'; 2 | export * from './leveldb-wallet-store'; 3 | export * from './leveldb-order-store'; 4 | export * from './leveldb-dlc-store'; 5 | export * from './leveldb-irc-store'; 6 | export * from './leveldb-oracle-store'; 7 | export * from './leveldb-info-store'; 8 | export * from './leveldb-gossip-store'; 9 | -------------------------------------------------------------------------------- /packages/leveldb/lib/leveldb-base.ts: -------------------------------------------------------------------------------- 1 | import { ClassicLevel } from 'classic-level'; 2 | import fs from 'fs'; 3 | 4 | export abstract class LeveldbBase { 5 | protected _path: string; 6 | protected _db: ClassicLevel; 7 | 8 | constructor(path: string) { 9 | this._path = path; 10 | fs.mkdirSync(this._path, { recursive: true }); 11 | this._db = new ClassicLevel(this._path, { 12 | keyEncoding: 'buffer', 13 | valueEncoding: 'buffer', 14 | }); 15 | } 16 | 17 | public async open(): Promise { 18 | return this._db.open(); 19 | } 20 | 21 | public async close(): Promise { 22 | return this._db.close(); 23 | } 24 | 25 | protected async _safeGet(key: Buffer): Promise { 26 | try { 27 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 28 | return ((await this._db.get(key)) as unknown) as T; 29 | } catch (err) { 30 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 31 | if (err.notFound) return; 32 | else throw err; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/leveldb/lib/leveldb-info-store.ts: -------------------------------------------------------------------------------- 1 | import { DlcInfoV0 } from '@node-dlc/messaging'; 2 | 3 | import { LeveldbBase } from './leveldb-base'; 4 | 5 | enum Prefix { 6 | DlcInfoV0 = 90, 7 | } 8 | 9 | export type InfoValue = 10 | | 'offer' 11 | | 'accept' 12 | | 'sign' 13 | | 'cancel' 14 | | 'close' 15 | | 'transactions'; 16 | 17 | export class LeveldbInfoStore extends LeveldbBase { 18 | public async findDlcInfo(): Promise { 19 | const key = Buffer.concat([Buffer.from([Prefix.DlcInfoV0])]); 20 | const raw = await this._safeGet(key); 21 | if (!raw) return; 22 | return DlcInfoV0.deserialize(raw); 23 | } 24 | 25 | public async saveDlcInfo(dlcInfo: DlcInfoV0): Promise { 26 | const value = dlcInfo.serialize(); 27 | const key = Buffer.concat([Buffer.from([Prefix.DlcInfoV0])]); 28 | await this._db.put(key, value); 29 | } 30 | 31 | public async deleteDlcInfo(): Promise { 32 | const key = Buffer.concat([Buffer.from([Prefix.DlcInfoV0])]); 33 | await this._db.del(key); 34 | } 35 | 36 | public async incrementDlcInfoValues(values: InfoValue[]): Promise { 37 | const dlcInfo = await this.findDlcInfo(); 38 | for (const value of values) { 39 | switch (value) { 40 | case 'offer': 41 | dlcInfo.numDlcOffers += 1; 42 | break; 43 | case 'accept': 44 | dlcInfo.numDlcAccepts += 1; 45 | break; 46 | case 'sign': 47 | dlcInfo.numDlcSigns += 1; 48 | break; 49 | case 'cancel': 50 | dlcInfo.numDlcCancels += 1; 51 | break; 52 | case 'close': 53 | dlcInfo.numDlcCloses += 1; 54 | break; 55 | case 'transactions': 56 | dlcInfo.numDlcTransactions += 1; 57 | break; 58 | } 59 | } 60 | await this.saveDlcInfo(dlcInfo); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/leveldb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/leveldb", 3 | "version": "0.24.1", 4 | "description": "DLC LevelDB", 5 | "scripts": { 6 | "start": "npm run build && node dist/index.js", 7 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 8 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 10 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 11 | "prepublish": "npm run build" 12 | }, 13 | "keywords": [ 14 | "dlc", 15 | "leveldb" 16 | ], 17 | "author": "Atomic Finance ", 18 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/leveldb", 19 | "license": "MIT", 20 | "main": "dist/index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 24 | }, 25 | "dependencies": { 26 | "@node-dlc/bitcoin": "^0.24.1", 27 | "@node-dlc/bufio": "^0.24.1", 28 | "@node-dlc/core": "^0.24.1", 29 | "@node-dlc/crypto": "^0.24.1", 30 | "@node-dlc/messaging": "^0.24.1", 31 | "@node-dlc/wire": "^0.24.1", 32 | "classic-level": "^3.0.0", 33 | "cryptr": "^6.0.2", 34 | "dotenv": "^8.2.0", 35 | "express": "^4.17.1", 36 | "helmet": "^3.23.3", 37 | "morgan": "1.10.0", 38 | "winston": "3.3.3" 39 | }, 40 | "devDependencies": { 41 | "bip39": "3.0.3" 42 | }, 43 | "publishConfig": { 44 | "access": "public" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/leveldb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "skipLibCheck": true 6 | }, 7 | "include": ["./lib"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/logger/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/logger 2 | 3 | ![Build Status](https://github.com/AtomicFinance/node-dlc/actions/workflows/main.yml/badge.svg) 4 | [![Standard Code Style](https://img.shields.io/badge/codestyle-standard-brightgreen.svg)](https://github.com/standard/standard) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](../../LICENSE) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i @node-dlc/logger 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/logger/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/logger/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logger'; 2 | export * from './log-level'; 3 | export * from './transports/console-transport'; 4 | export * from './transports/file-transport'; 5 | -------------------------------------------------------------------------------- /packages/logger/lib/log-level.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Log level 3 | */ 4 | export enum LogLevel { 5 | Trace = 'TRC', 6 | Debug = 'DBG', 7 | Info = 'INF', 8 | Warn = 'WRN', 9 | Error = 'ERR', 10 | } 11 | -------------------------------------------------------------------------------- /packages/logger/lib/transport.ts: -------------------------------------------------------------------------------- 1 | export interface ITransport { 2 | write(line: string): void; 3 | } 4 | -------------------------------------------------------------------------------- /packages/logger/lib/transports/console-transport.ts: -------------------------------------------------------------------------------- 1 | import { ITransport } from '../transport'; 2 | 3 | export class ConsoleTransport implements ITransport { 4 | public console: Console; 5 | 6 | constructor(console: Console) { 7 | this.console = console; 8 | } 9 | 10 | public write(line: string): void { 11 | this.console.log(line); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/logger/lib/transports/file-transport.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { ITransport } from '../transport'; 4 | 5 | export class FileTransport implements ITransport { 6 | public filePath: string; 7 | public fileDescriptor: number; 8 | 9 | constructor(filepath: string) { 10 | this.filePath = filepath; 11 | this.fileDescriptor = fs.openSync(filepath, 'a', 0o666); 12 | } 13 | 14 | public write(line: string): void { 15 | fs.writeSync(this.fileDescriptor, line + '\n'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/logger/lib/util.ts: -------------------------------------------------------------------------------- 1 | import { LogLevel } from './log-level'; 2 | 3 | /** 4 | * Helper function to determine if a log message is at the appropraite 5 | * level to be included in the logs 6 | * @param myLevel 7 | * @param msgLevel 8 | */ 9 | export function shouldLog(myLevel: LogLevel, msgLevel: LogLevel): boolean { 10 | switch (myLevel) { 11 | case LogLevel.Trace: 12 | return true; 13 | case LogLevel.Debug: 14 | return ( 15 | msgLevel === LogLevel.Debug || 16 | msgLevel === LogLevel.Info || 17 | msgLevel === LogLevel.Warn || 18 | msgLevel === LogLevel.Error 19 | ); 20 | case LogLevel.Info: 21 | return ( 22 | msgLevel === LogLevel.Info || 23 | msgLevel === LogLevel.Warn || 24 | msgLevel === LogLevel.Error 25 | ); 26 | case LogLevel.Warn: 27 | return msgLevel === LogLevel.Warn || msgLevel === LogLevel.Error; 28 | case LogLevel.Error: 29 | return msgLevel === LogLevel.Error; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/logger/logger: -------------------------------------------------------------------------------- 1 | ../../../logger -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/logger", 3 | "version": "0.24.1", 4 | "description": "DLC Logger", 5 | "scripts": { 6 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 7 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 8 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 10 | "prepublish": "npm run build" 11 | }, 12 | "keywords": [ 13 | "dlc", 14 | "logger" 15 | ], 16 | "author": "Atomic Finance ", 17 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/logger", 18 | "license": "MIT", 19 | "main": "dist/index.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 23 | }, 24 | "publishConfig": { 25 | "access": "public" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | }, 6 | "include": ["./lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/messaging/__tests__/_test-utils.ts: -------------------------------------------------------------------------------- 1 | import { ILogger, Logger } from '@node-dlc/logger'; 2 | import sinon from 'sinon'; 3 | 4 | export function createFakeLogger(): ILogger { 5 | const fake = sinon.createStubInstance(Logger); 6 | fake.sub = createFakeLogger as any; 7 | return fake; 8 | } 9 | 10 | export function wait(ms: number): Promise { 11 | return new Promise((resolve) => setTimeout(resolve, ms)); 12 | } 13 | -------------------------------------------------------------------------------- /packages/messaging/__tests__/messages/DlcCancelV0.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { DlcCancelV0 } from '../../lib/messages/DlcCancel'; 4 | 5 | describe('DlcCancelV0', () => { 6 | const contractId = Buffer.from( 7 | 'c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269', 8 | 'hex', 9 | ); 10 | 11 | describe('serialize', () => { 12 | it('serializes', () => { 13 | const instance = new DlcCancelV0(); 14 | 15 | instance.contractId = contractId; 16 | 17 | instance.cancelType = 0; 18 | 19 | expect(instance.serialize().toString("hex")).to.equal( 20 | "cbcc" + 21 | "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" + 22 | "00" // cancel type 23 | ); // prettier-ignore 24 | }); 25 | }); 26 | 27 | describe('deserialize', () => { 28 | it('deserializes', () => { 29 | const buf = Buffer.from( 30 | "cbcc" + 31 | "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" + 32 | "00" // cancel type 33 | , "hex" 34 | ); // prettier-ignore 35 | 36 | const instance = DlcCancelV0.deserialize(buf); 37 | 38 | expect(instance.contractId).to.deep.equal(contractId); 39 | expect(instance.cancelType).to.equal(0); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/messaging/__tests__/messages/EnumEventDescriptorV0.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { EnumEventDescriptorV0 } from '../../lib/messages/EventDescriptor'; 4 | 5 | describe('EnumEventDescriptorV0', () => { 6 | const outcomeOne = Buffer.from('64756d6d7931', 'hex').toString(); 7 | 8 | const outcomeTwo = Buffer.from('64756d6d7932', 'hex').toString(); 9 | 10 | describe('serialize', () => { 11 | it('serializes', () => { 12 | const instance = new EnumEventDescriptorV0(); 13 | 14 | instance.length = BigInt(16); 15 | instance.outcomes = [outcomeOne, outcomeTwo]; 16 | 17 | expect(instance.serialize().toString("hex")).to.equal( 18 | "fdd806" + // type 19 | "10" + // length 20 | "0002" + // num_outcomes 21 | "06" + // outcome_1_len 22 | "64756d6d7931" + // outcome_1 23 | "06" + // outcome_2_len 24 | "64756d6d7932" // outcome_2 25 | ); // prettier-ignore 26 | }); 27 | }); 28 | 29 | describe('deserialize', () => { 30 | it('deserializes', () => { 31 | const buf = Buffer.from( 32 | "fdd806" + // type 33 | "10" + // length 34 | "0002" + // num_outcomes 35 | "06" + // outcome_1_len 36 | "64756d6d7931" + // outcome_1 37 | "06" + // outcome_2_len 38 | "64756d6d7932" // outcome_2 39 | , "hex" 40 | ); // prettier-ignore 41 | 42 | const instance = EnumEventDescriptorV0.deserialize(buf); 43 | 44 | expect(Number(instance.outcomes[0].length)).to.equal(outcomeOne.length); 45 | expect(instance.outcomes[0]).to.deep.equal(outcomeOne); 46 | expect(Number(instance.outcomes[1].length)).to.equal(outcomeTwo.length); 47 | expect(instance.outcomes[1]).to.deep.equal(outcomeTwo); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/messaging/__tests__/messages/OracleIdentifierV0.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { OracleIdentifierV0 } from '../../lib/messages/OracleIdentifierV0'; 4 | 5 | describe('OracleIdentifierV0', () => { 6 | const oraclePubkey = Buffer.from( 7 | '5d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9', 8 | 'hex', 9 | ); 10 | 11 | const oracleName = 'atomic'; 12 | 13 | describe('serialize', () => { 14 | it('serializes', () => { 15 | const instance = new OracleIdentifierV0(); 16 | 17 | instance.length = BigInt(64); 18 | instance.oracleName = oracleName; 19 | instance.oraclePubkey = oraclePubkey; 20 | 21 | expect(instance.serialize().toString("hex")).to.equal( 22 | "fdf020" + // type oracle_identifier 23 | "27" + // length 24 | "06" + // oracle_name length 25 | "61746f6d6963" + // oracle_name 26 | "5d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9" // oracle_pubkey 27 | ); // prettier-ignore 28 | }); 29 | }); 30 | 31 | describe('deserialize', () => { 32 | it('deserializes', () => { 33 | const buf = Buffer.from( 34 | "fdf020" + // type oracle_identifier 35 | "27" + // length 36 | "06" + // oracle_name length 37 | "61746f6d6963" + // oracle_name 38 | "5d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9" // oracle_pubkey 39 | , "hex" 40 | ); // prettier-ignore 41 | 42 | const instance = OracleIdentifierV0.deserialize(buf); 43 | 44 | expect(instance.oracleName).to.equal(oracleName); 45 | expect(instance.oraclePubkey).to.deep.equal(oraclePubkey); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/messaging/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/messaging/lib/chain/ChainMemoryStore.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/require-await */ 2 | import { DlcTransactionsV0 } from '../messages/DlcTransactions'; 3 | import { IDlcStore } from './DlcStore'; 4 | 5 | /** 6 | * In-memory implementation of the IDlcStore. 7 | */ 8 | export class ChainMemoryStore implements IDlcStore { 9 | private _dlcTxs = new Map(); 10 | 11 | public get dlcTransactionsListCount(): number { 12 | return this._dlcTxs.size; 13 | } 14 | 15 | public async findDlcTransactionsList(): Promise { 16 | return Array.from(this._dlcTxs.values()); 17 | } 18 | 19 | public async findDlcTransactions( 20 | contractId: Buffer, 21 | ): Promise { 22 | return this._dlcTxs.get(contractId); 23 | } 24 | 25 | public async saveDlcTransactions( 26 | dlcTransactions: DlcTransactionsV0, 27 | ): Promise { 28 | this._dlcTxs.set(dlcTransactions.contractId, dlcTransactions); 29 | } 30 | 31 | public async deleteDlcTransactions(contractId: Buffer): Promise { 32 | this._dlcTxs.delete(contractId); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/messaging/lib/chain/DlcStore.ts: -------------------------------------------------------------------------------- 1 | import { DlcTransactionsV0 } from '../messages/DlcTransactions'; 2 | 3 | /** 4 | * Interface for storing, finding, and deleting dlc messages. 5 | */ 6 | export interface IDlcStore { 7 | findDlcTransactionsList(): Promise; 8 | findDlcTransactions(contractId: Buffer): Promise; 9 | saveDlcTransactions(dlcTransactions: DlcTransactionsV0): Promise; 10 | deleteDlcTransactions(contractId: Buffer): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /packages/messaging/lib/chain/IChainFilterChainClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The interface required by the Chainfilter to perform on-chain validation checks 3 | * of messages. A simplified interface is required by the Chainfilter. The actual 4 | * chain client implementation may offer a broader set of feautures. Additionally, 5 | * an adapter could be constructred to make an chain client conform to that required 6 | * by the Chainfilter. 7 | */ 8 | export interface IChainFilterChainClient { 9 | getBlockchainInfo(): Promise; 10 | getBlockHash(height: number): Promise; 11 | getBlock(hash: string): Promise; 12 | getRawBlock(hash: string): Promise; 13 | getTransaction(txId: string): Promise; 14 | getUtxo(txId: string, voutIdx: number): Promise; 15 | waitForSync(): Promise; 16 | } 17 | 18 | export type HasBlocks = { 19 | blocks: number; 20 | }; 21 | 22 | export type HasBestBlockHash = { 23 | bestblockhash: string; 24 | }; 25 | 26 | export type HasTxStrings = { 27 | tx: string[]; 28 | }; 29 | 30 | export type HasHeight = { 31 | height: number; 32 | }; 33 | 34 | export type HasHash = { 35 | hash: string; 36 | }; 37 | 38 | export type HasTransaction = { 39 | txid: string; 40 | blockhash: string; 41 | }; 42 | 43 | export type HasScriptPubKey = { 44 | scriptPubKey: HasHex; 45 | }; 46 | 47 | export type HasValue = { 48 | value: number; 49 | }; 50 | 51 | export type HasConfirmations = { 52 | confirmations: string; 53 | }; 54 | 55 | export type HasHex = { 56 | hex: string; 57 | }; 58 | -------------------------------------------------------------------------------- /packages/messaging/lib/domain/Address.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { MessageType } from '../MessageType'; 4 | 5 | export class Address { 6 | public static type = MessageType.NodeAnnouncementAddress; 7 | 8 | public static deserialize(buf: Buffer): Address { 9 | const reader = new BufferReader(buf); 10 | reader.readBigSize(); // read off type 11 | reader.readBigSize(); // read size 12 | 13 | const hostLen = reader.readBigSize(); 14 | const hostBuf = reader.readBytes(Number(hostLen)); 15 | const host = hostBuf.toString(); 16 | const port = reader.readUInt16BE(); 17 | 18 | const instance = new Address(host, port); 19 | 20 | return instance; 21 | } 22 | 23 | public type = Address.type; 24 | 25 | /** 26 | * String notation representation of the host 27 | */ 28 | public host: string; 29 | 30 | /** 31 | * Port number 32 | */ 33 | public port: number; 34 | 35 | /** 36 | * Base class representing a network address 37 | */ 38 | constructor(host: string, port: number) { 39 | this.host = host; 40 | this.port = port; 41 | } 42 | 43 | public toString(): string { 44 | return `${this.host}:${this.port}`; 45 | } 46 | 47 | /** 48 | * Serializes the dlc_transactions_v0 message into a Buffer 49 | */ 50 | public serialize(): Buffer { 51 | const writer = new BufferWriter(); 52 | writer.writeBigSize(this.type); 53 | 54 | const dataWriter = new BufferWriter(); 55 | dataWriter.writeBigSize(this.host.length); 56 | dataWriter.writeBytes(Buffer.from(this.host)); 57 | dataWriter.writeUInt16BE(this.port); 58 | 59 | writer.writeBigSize(dataWriter.size); 60 | writer.writeBytes(dataWriter.toBuffer()); 61 | 62 | return writer.toBuffer(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/messaging/lib/messages/DlcIds.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { MessageType } from '../MessageType'; 4 | import { IDlcMessage } from './DlcMessage'; 5 | 6 | export abstract class DlcIds { 7 | public static deserialize(buf: Buffer): DlcIdsV0 { 8 | const reader = new BufferReader(buf); 9 | 10 | const type = Number(reader.readUInt16BE()); 11 | 12 | switch (type) { 13 | case MessageType.DlcIdsV0: 14 | return DlcIdsV0.deserialize(buf); 15 | default: 16 | throw new Error(`DLC IDs message type must be DlcIdsV0`); 17 | } 18 | } 19 | 20 | public abstract type: number; 21 | 22 | public abstract serialize(): Buffer; 23 | } 24 | 25 | /** 26 | * DlcIds message contains list of buffers 27 | */ 28 | export class DlcIdsV0 extends DlcIds implements IDlcMessage { 29 | public static type = MessageType.DlcIdsV0; 30 | 31 | /** 32 | * Deserializes an dlc_ids_v0 message 33 | * @param buf 34 | */ 35 | public static deserialize(buf: Buffer): DlcIdsV0 { 36 | const instance = new DlcIdsV0(); 37 | const reader = new BufferReader(buf); 38 | 39 | reader.readUInt16BE(); // read type 40 | const idsLen = reader.readBigSize(); // ids length 41 | 42 | for (let i = 0; i < idsLen; i++) { 43 | instance.ids.push(reader.readBytes(32)); 44 | } 45 | 46 | return instance; 47 | } 48 | 49 | /** 50 | * The type for dlc_ids_v0 message 51 | */ 52 | public type = DlcIdsV0.type; 53 | 54 | public ids: Buffer[] = []; 55 | 56 | /** 57 | * Serializes the dlc_ids_v0 message into a Buffer 58 | */ 59 | public serialize(): Buffer { 60 | const writer = new BufferWriter(); 61 | writer.writeUInt16BE(this.type); 62 | writer.writeBigSize(this.ids.length); 63 | for (const id of this.ids) { 64 | writer.writeBytes(id); 65 | } 66 | 67 | return writer.toBuffer(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/messaging/lib/messages/IWireMessage.ts: -------------------------------------------------------------------------------- 1 | import { MessageType } from '../MessageType'; 2 | 3 | export interface IWireMessage { 4 | type: MessageType; 5 | serialize(): Buffer; 6 | } 7 | -------------------------------------------------------------------------------- /packages/messaging/lib/messages/ScriptWitnessV0.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | /** 4 | * ScriptWitness is the data for a witness element in a witness stack. 5 | * An empty witness_stack is an error, as every input must be Segwit. 6 | * Witness elements should not include their length as part of the witness 7 | * data. 8 | */ 9 | export class ScriptWitnessV0 { 10 | /** 11 | * Deserializes an script_witness_v0 message 12 | * @param buf 13 | */ 14 | public static deserialize(buf: Buffer): ScriptWitnessV0 { 15 | const instance = new ScriptWitnessV0(); 16 | const reader = new BufferReader(buf); 17 | 18 | instance.length = reader.readUInt16BE(); 19 | instance.witness = reader.readBytes(instance.length); 20 | 21 | return instance; 22 | } 23 | 24 | public static getWitness(reader: BufferReader): Buffer { 25 | const length = reader.readUInt16BE(); 26 | const body = reader.readBytes(Number(length)); 27 | 28 | const writer = new BufferWriter(); 29 | writer.writeUInt16BE(length); 30 | writer.writeBytes(body); 31 | 32 | return writer.toBuffer(); 33 | } 34 | 35 | public length: number; 36 | 37 | public witness: Buffer; 38 | 39 | /** 40 | * Converts script_witness_v0 to JSON 41 | */ 42 | public toJSON(): IScriptWitnessV0JSON { 43 | return { 44 | witness: this.witness.toString('hex'), 45 | }; 46 | } 47 | 48 | /** 49 | * Serializes the script_witness_v0 message into a Buffer 50 | */ 51 | public serialize(): Buffer { 52 | const writer = new BufferWriter(); 53 | 54 | writer.writeUInt16BE(this.witness.length); 55 | writer.writeBytes(this.witness); 56 | 57 | return writer.toBuffer(); 58 | } 59 | } 60 | 61 | export interface IScriptWitnessV0JSON { 62 | witness: string; 63 | } 64 | -------------------------------------------------------------------------------- /packages/messaging/lib/messages/Tlv.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | export class Tlv { 4 | /** 5 | * Deserializes an tlv message 6 | * @param buf 7 | */ 8 | public static deserialize(buf: Buffer): Tlv { 9 | const instance = new Tlv(); 10 | const reader = new BufferReader(buf); 11 | 12 | instance.type = Number(reader.readBigSize()); // read type 13 | instance.length = reader.readBigSize(); 14 | instance.body = reader.readBytes(Number(instance.length)); 15 | 16 | return instance; 17 | } 18 | 19 | public type: number; 20 | 21 | public length: bigint; 22 | 23 | public body: Buffer; 24 | 25 | /** 26 | * Serializes the tlv message into a Buffer 27 | */ 28 | public serialize(): Buffer { 29 | const writer = new BufferWriter(); 30 | writer.writeBigSize(this.type); 31 | const dataWriter = new BufferWriter(); 32 | 33 | dataWriter.writeBytes(this.body); 34 | 35 | writer.writeBigSize(dataWriter.size); 36 | writer.writeBytes(dataWriter.toBuffer()); 37 | 38 | return writer.toBuffer(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/messaging/lib/serialize/deserializeTlv.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | export function deserializeTlv(reader: BufferReader): ITlv { 4 | const type = reader.readBigSize(); 5 | const length = reader.readBigSize(); 6 | const body = reader.readBytes(Number(length)); 7 | 8 | return { type, length, body }; 9 | } 10 | 11 | interface ITlv { 12 | type: bigint; 13 | length: bigint; 14 | body: Buffer; 15 | } 16 | -------------------------------------------------------------------------------- /packages/messaging/lib/serialize/getTlv.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | // TODO: add unit tests 4 | export function getTlv(reader: BufferReader): Buffer { 5 | const type = reader.readBigSize(); 6 | const length = reader.readBigSize(); 7 | const body = reader.readBytes(Number(length)); 8 | 9 | const writer = new BufferWriter(); 10 | writer.writeBigSize(type); 11 | writer.writeBigSize(length); 12 | writer.writeBytes(body); 13 | 14 | return writer.toBuffer(); 15 | } 16 | 17 | export function skipTlv(reader: BufferReader): void { 18 | reader.readBigSize(); 19 | const length = reader.readBigSize(); 20 | reader.readBytes(Number(length)); 21 | } 22 | -------------------------------------------------------------------------------- /packages/messaging/lib/serialize/readTlvs.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | /** 4 | * Reads TLVs from a reader until the entire stream is processed. The handler is 5 | * responsible for doing something with the data bytes. 6 | * @param reader 7 | * @param handler 8 | */ 9 | export function readTlvs( 10 | reader: BufferReader, 11 | handler: (type: bigint, value: BufferReader) => boolean, 12 | ) { 13 | let lastType: bigint; 14 | while (!reader.eof) { 15 | const type = reader.readBigSize(); 16 | const len = reader.readBigSize(); 17 | const value = reader.readBytes(Number(len)); 18 | const valueReader = new BufferReader(value); 19 | 20 | if (type <= lastType) { 21 | throw new Error('Invalid TLV stream'); 22 | } 23 | 24 | const isEven = type % BigInt(2) === BigInt(0); 25 | const wasHandled = handler(type, valueReader); 26 | 27 | if (!wasHandled && isEven) { 28 | throw new Error('Unknown even type'); 29 | } 30 | 31 | if (wasHandled && !valueReader.eof) { 32 | throw new Error('Non-canonical length'); 33 | } 34 | 35 | lastType = type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/messaging/lib/util.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/messaging/lib/validation/validate.ts: -------------------------------------------------------------------------------- 1 | export const validateBuffer = ( 2 | value: Buffer, 3 | key: string, 4 | className: string, 5 | expectedLength?: number, 6 | ): void => { 7 | if (value === undefined) throw Error(`${className} ${key} is undefined`); 8 | if (value.length === 0) throw Error(`${className} ${key} length cannot be 0`); 9 | if (expectedLength && value.length !== expectedLength) 10 | throw Error(`${className} ${key} length expected to be ${expectedLength}`); 11 | }; 12 | 13 | export const validateBigInt = ( 14 | value: bigint, 15 | key: string, 16 | className: string, 17 | ): void => { 18 | if (value === undefined) throw Error(`${className} ${key} is undefined`); 19 | if (typeof value !== 'bigint') 20 | throw Error(`${className} ${key} must be type bigint`); 21 | if (value === BigInt(0)) 22 | throw Error(`${className} ${key} must be greater than 0`); 23 | }; 24 | 25 | export const validateNumber = ( 26 | value: number, 27 | key: string, 28 | className: string, 29 | ): void => { 30 | if (value === undefined) throw Error(`${className} ${key} is undefined`); 31 | if (typeof value !== 'number') 32 | throw Error(`${className} ${key} must be type number`); 33 | if (value === 0) throw Error(`${className} ${key} must be greater than 0`); 34 | }; 35 | -------------------------------------------------------------------------------- /packages/messaging/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/messaging", 3 | "version": "0.24.1", 4 | "description": "DLC Messaging Protocol", 5 | "scripts": { 6 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 7 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 8 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 10 | "prepublish": "npm run build" 11 | }, 12 | "keywords": [ 13 | "dlc", 14 | "messaging protocol" 15 | ], 16 | "author": "Atomic Finance ", 17 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/messaging", 18 | "license": "MIT", 19 | "main": "dist/index.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 23 | }, 24 | "dependencies": { 25 | "@node-dlc/bitcoin": "^0.24.1", 26 | "@node-dlc/bufio": "^0.24.1", 27 | "@node-dlc/checksum": "^0.24.1", 28 | "@node-dlc/common": "^0.24.1", 29 | "@node-dlc/crypto": "^0.24.1", 30 | "@node-dlc/wire": "^0.24.1", 31 | "bip-schnorr": "0.6.3", 32 | "bitcoin-networks": "^1.0.0", 33 | "bitcoinjs-lib": "6.1.7", 34 | "secp256k1": "^4.0.3" 35 | }, 36 | "devDependencies": { 37 | "@node-dlc/logger": "^0.24.1", 38 | "@types/node": "18.11.9", 39 | "@types/secp256k1": "^4.0.3", 40 | "sinon": "14.0.0" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/messaging/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | }, 6 | "include": ["./lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/noise/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/noise/lib/handshake-state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * States that the handshake process can be in. States depend on 3 | * whether the socket is the connection Initiator or Responder. 4 | * 5 | * Initiator: 6 | * 1. create and send Iniatitor act1 and transition to 7 | * AWAITING_RESPONDER_REPLY 8 | * 2. process the Responder's reply as act2 9 | * 3. create Initiator act3 reply to complete the handshake 10 | * and transitions to READY 11 | * 12 | * Responder: 13 | * 1. begins in AWAITING_INITIATOR waiting to receive act1 14 | * 2. processes act1 and creates a reply as act2 and transitions 15 | * to AWAITING_INITIATOR_REPLY 16 | * 3. processes the Initiator's reply to complete the handshake 17 | * and transition to READY 18 | */ 19 | export enum HANDSHAKE_STATE { 20 | /** 21 | * Initial state for the Initiator. Initiator will transition to 22 | * AWAITING_RESPONDER_REPLY once act1 is completed and sent. 23 | */ 24 | INITIATOR_INITIATING = 0, 25 | 26 | /** 27 | * Responders begin in this state and wait for the Intiator to begin 28 | * the handshake. Sockets originating from the NoiseServer will 29 | * begin in this state. 30 | */ 31 | AWAITING_INITIATOR = 1, 32 | 33 | /** 34 | * Initiator has sent act1 and is awaiting the reply from the responder. 35 | * Once received, the intiator will create the reply 36 | */ 37 | AWAITING_RESPONDER_REPLY = 2, 38 | 39 | /** 40 | * Responder has sent a reply to the inititator, the Responder will be 41 | * waiting for the final stage of the handshake sent by the Initiator. 42 | */ 43 | AWAITING_INITIATOR_REPLY = 3, 44 | 45 | /** 46 | * Responder/Initiator have completed the handshake and we're ready to 47 | * start sending and receiving over the socket. 48 | */ 49 | READY = 100, 50 | } 51 | -------------------------------------------------------------------------------- /packages/noise/lib/noise-error.ts: -------------------------------------------------------------------------------- 1 | export class NoiseError extends Error { 2 | public module = 'noise'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/noise/lib/noise-server-listen-options.ts: -------------------------------------------------------------------------------- 1 | export type NoiseServerListenOptions = { 2 | /** 3 | * Optional port to listen on. If port is omitted or is 0, 4 | * the operating system will assign an arbitrary unused port, which can be retrieved 5 | * by using server.address().port after the 'listening' event has been emitted. 6 | */ 7 | port?: number; 8 | 9 | /** 10 | * Optional host to listen on. If host is omitted, the server 11 | * will accept connections on the unspecified IPv6 address (::) when IPv6 is available, 12 | * or the unspecified IPv4 address (0.0.0.0) otherwise. In most operating systems, 13 | * listening to the unspecified IPv6 address (::) may cause NoiseSocket to also 14 | * listen on the unspecified IPv4 address (0.0.0.0). 15 | */ 16 | host?: string; 17 | 18 | /** 19 | * Optional value to specify the maximum length of the 20 | * queue of pending connections. The actual length will be determined by the OS through 21 | * sysctl settings such as tcp_max_syn_backlog and somaxconn on Linux. The default value 22 | * of this parameter is 511 (not 512). 23 | */ 24 | backlog?: number; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/noise/lib/noise-server-options.ts: -------------------------------------------------------------------------------- 1 | export type NoiseServerOptions = { 2 | /** 3 | * Local private key as a 32-byte private key for elliptic curve 4 | * secp256k1 used by the server 5 | */ 6 | ls: Buffer; 7 | 8 | /** 9 | * Function for creating the ephemeral private key used by each 10 | * connection 11 | */ 12 | esFactory?: () => Buffer; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/noise/lib/noise-socket-options.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from '@node-dlc/logger'; 2 | import { Socket } from 'net'; 3 | 4 | import { NoiseState } from './noise-state'; 5 | 6 | export type NoiseSocketOptions = { 7 | /** 8 | * Standard TCP Socket from the net module that will be wrapped 9 | */ 10 | socket: Socket; 11 | 12 | /** 13 | * State machine for noise connections that is injected into the socket 14 | */ 15 | noiseState: NoiseState; 16 | 17 | /** 18 | * Remote public key when connecting to a remote server. When provided, 19 | * makes the socket the noise state initiator. 20 | */ 21 | rpk?: Buffer; 22 | 23 | /** 24 | * Logger that can be optionally used by the noise system 25 | */ 26 | logger?: ILogger; 27 | 28 | /** 29 | * High watermark for the stream 30 | */ 31 | highWaterMark?: number; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/noise/lib/noise-state-options.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from '@node-dlc/logger'; 2 | 3 | export type NoiseStateOptions = { 4 | /** 5 | * Local private key as a 32-byte buffer 6 | */ 7 | ls: Buffer; 8 | 9 | /** 10 | * Ephemeral private key as a 32-byte 11 | */ 12 | es: Buffer; 13 | 14 | /** 15 | * Logger to use for NoiseState 16 | */ 17 | logger?: ILogger; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/noise/lib/read-state.ts: -------------------------------------------------------------------------------- 1 | export enum READ_STATE { 2 | READY_FOR_LEN = 2, 3 | READY_FOR_BODY = 3, 4 | BLOCKED = 4, 5 | } 6 | -------------------------------------------------------------------------------- /packages/noise/noise: -------------------------------------------------------------------------------- 1 | ../../../noise -------------------------------------------------------------------------------- /packages/noise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/noise", 3 | "version": "0.24.1", 4 | "description": "BOLT 8 Lightning Network Noise Protocol Socket", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts --exclude \"__integration__\" --exclude \"__tests__\" ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\" --recursive \"__integration__/**/*.spec.*\"", 8 | "lint": "../../node_modules/.bin/eslint lib/**/*", 9 | "lint:fix": "../../node_modules/.bin/eslint lib/**/* --fix", 10 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 11 | "prepublish": "npm run build" 12 | }, 13 | "keywords": [ 14 | "lightning network", 15 | "bolt-8", 16 | "noise protocol", 17 | "socket", 18 | "server", 19 | "bitcoin" 20 | ], 21 | "author": "Brian Mancini ", 22 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/noise", 23 | "license": "MIT", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 27 | }, 28 | "dependencies": { 29 | "@node-dlc/crypto": "^0.24.1", 30 | "@node-dlc/logger": "^0.24.1" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/noise/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/noise/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/transport/README.md: -------------------------------------------------------------------------------- 1 | # @node-dlc/transport 2 | 3 | ![Build Status](https://github.com/AtomicFinance/node-dlc/actions/workflows/main.yml/badge.svg) 4 | [![Standard Code Style](https://img.shields.io/badge/codestyle-standard-brightgreen.svg)](https://github.com/standard/standard) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](../../LICENSE) 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i @node-dlc/transport 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/transport/__tests__/irc/data/ircd.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDH5pYbcECKUrbRbUXKUu7lMCgb9UkPi4+Ur9f0LYdspHZJlv0S 3 | yBn4RpJOl8EsMhWI+houY3mBlcCL/DwiGfMDk5TSomyrI6eONFworokTJpG2h0f0 4 | cWnGdDW1zu8Z1odo047NWzwwv2mU03fkZmzfCclAzjKkDMMqP34mPl5TnwIDAQAB 5 | AoGAJslK3tAM9cnOxxvYqsUkrTuGzMXvAyElHshvsmUTHbVbbjPprrc8sruer7kq 6 | NhURsJ42bkHG1ankzkSGtmcqi3LdBBhVLm5gyog2JxQlTxvUVOPvyrOsQkl3uDwL 7 | aZqGTESHlLx7jhOKgiImqo0uGxNy46tzsHbpFGAeqTYcYKECQQD6faxqytMpMc/h 8 | zcrWsRhe7Omj5D6VdrbkGkM8razn4Oyr42p8Xylcde2MlnTiTAL5ElxlLd4PYsLD 9 | hKme/M5tAkEAzEwT1GU7CYjPdHHfsHUbDIHBh0BOJje2TXhDOa5tiZbOZevIk6TZ 10 | V6p/9zjLe5RAc/dpzHv1C+vQOkhgvoNyuwJARwjGkU5NTXxTwGwUnoeAKsMyioia 11 | etY8jTkpYha6VtOBKkmGlBiEaTUEFX9BTD9UBIABdavpMiHGq51+YJi+jQJAGYic 12 | pdwtH8jwnM4qtgQ86DhDduMLoW0vJMmWJVxuplap30Uz4XgmDfXqXnzDueNSluvi 13 | VkNb4iyL7uzi4ozNRwJALT0vP65RQ2d7OUEwB4XZFExKYzHADiFtw0NZtcWRW6y3 14 | rN0uXMxEZ6vRQurVjO9GhB76fAo/UooX0MVF0ShFNQ== 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /packages/transport/__tests__/irc/data/ircd.pem: -------------------------------------------------------------------------------- 1 | 2 | -----BEGIN CERTIFICATE----- 3 | MIICojCCAgugAwIBAgIJAMid3M25tUeUMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV 4 | BAYTAlpaMREwDwYDVQQIDAhJbnRlcm5ldDEPMA0GA1UEBwwGZ2l0aHViMREwDwYD 5 | VQQKDAhub2RlLWlyYzEQMA4GA1UECwwHdGVzdGluZzESMBAGA1UEAwwJbG9jYWxo 6 | b3N0MB4XDTE1MDExMjIzNDg0MloXDTI1MDEwOTIzNDg0MlowajELMAkGA1UEBhMC 7 | WloxETAPBgNVBAgMCEludGVybmV0MQ8wDQYDVQQHDAZnaXRodWIxETAPBgNVBAoM 8 | CG5vZGUtaXJjMRAwDgYDVQQLDAd0ZXN0aW5nMRIwEAYDVQQDDAlsb2NhbGhvc3Qw 9 | gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMfmlhtwQIpSttFtRcpS7uUwKBv1 10 | SQ+Lj5Sv1/Qth2ykdkmW/RLIGfhGkk6XwSwyFYj6Gi5jeYGVwIv8PCIZ8wOTlNKi 11 | bKsjp440XCiuiRMmkbaHR/RxacZ0NbXO7xnWh2jTjs1bPDC/aZTTd+RmbN8JyUDO 12 | MqQMwyo/fiY+XlOfAgMBAAGjUDBOMB0GA1UdDgQWBBTUaumzrTJrl1goRRzOGgEO 13 | VNKFmjAfBgNVHSMEGDAWgBTUaumzrTJrl1goRRzOGgEOVNKFmjAMBgNVHRMEBTAD 14 | AQH/MA0GCSqGSIb3DQEBBQUAA4GBAGKppBE9mjk2zJPSxPcHl3RSpnPs5ZkuBLnK 15 | rxZ2bR9VJhoQEwtiZRxkSXSdooj3eJgzMobYMEhSvFibUeBuIppB7oacys2Bd+O1 16 | xzILcbgEPqsk5JFbYT9KD8r+sZy5Wa1A39eNkmdD/oWt9Mb1PLrDfM/melvZ9/vW 17 | oMSmMipK 18 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /packages/transport/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["**/*.ts"], 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /packages/transport/__tests__/util.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number): Promise { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/transport/lib/@types/irc/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'irc' { 2 | import events = require('events'); 3 | 4 | export interface Client extends events.EventEmitter { 5 | send(command: string, ...args: string[]): void; 6 | join(channel: string, callback: (arg: any) => void): void; 7 | part(channel: string, message: string): void; 8 | part(channel: string, callback: (arg: any) => void, message: string): void; 9 | say(target: string, message: string): void; 10 | ctcp(target: string, type: string, text: string): void; 11 | action(target: string, message: string): void; 12 | notice(target: string, message: string): void; 13 | whois(nick: string, callback: (arg: any) => void): void; 14 | list(args: string[]): void; 15 | connect(retryCount?: number, callback?: (arg: any) => void): void; 16 | disconnect(message?: string, callback?: (arg: any) => void): void; 17 | activateFloodProtection(interval?: number): void; 18 | } 19 | 20 | interface ClientClass { 21 | new (server: string, nick: string, opt?: ClientOptions): Client; 22 | } 23 | 24 | export interface ClientOptions { 25 | userName?: string; 26 | realName?: string; 27 | port?: number; 28 | localAddress?: string; 29 | debug?: boolean; 30 | showErrors?: boolean; 31 | autoRejoin?: boolean; 32 | autoConnect?: boolean; 33 | channels?: string[]; 34 | secure?: boolean; 35 | selfSigned?: boolean; 36 | certExpired?: boolean; 37 | floodProtection?: boolean; 38 | floodProtectionDelay?: number; 39 | sasl?: boolean; 40 | stripColors?: boolean; 41 | channelPrefixes?: string; 42 | messageSplit?: number; 43 | encoding?: string; 44 | } 45 | 46 | export const Client: ClientClass; 47 | 48 | export const colors: { 49 | wrap(color: string, text: string, reset_color?: string); 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /packages/transport/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './irc'; 2 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/ChannelType.ts: -------------------------------------------------------------------------------- 1 | export enum ChannelType { 2 | AtomicMarketPit = '#atomicmarket-pit', 3 | TestnetMarketPit = '#testnetmarket-pit', 4 | RegtestMarketPit = '#regtestmarket-pit', 5 | TestMarketPit = '#testmarket-pit', 6 | AtomicAltMarketPit = '#atomicaltmarket-pit', 7 | TestnetAltMarketPit = '#testnetaltmarket-pit', 8 | RegtestAltMarketPit = '#regtestaltmarket-pit', 9 | TestAltMarketPit = '#testaltmarket-pit', 10 | } 11 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/ILogger.ts: -------------------------------------------------------------------------------- 1 | export interface ILogger { 2 | area: string; 3 | instance: string; 4 | trace(...args: any[]): void; 5 | debug(...args: any[]): void; 6 | info(...args: any[]): void; 7 | warn(...args: any[]): void; 8 | error(...args: any[]): void; 9 | sub(area: string, instance?: string): ILogger; 10 | } 11 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/Servers.ts: -------------------------------------------------------------------------------- 1 | interface IrcServer { 2 | host: string; 3 | port: number; 4 | } 5 | 6 | const primary_server: IrcServer = { 7 | host: 'irc.darkscience.net', 8 | port: 6697, 9 | }; 10 | 11 | const secondary_server: IrcServer = { 12 | host: 'irc.hackint.org', 13 | port: 6697, 14 | }; 15 | 16 | const tertiary_server: IrcServer = { 17 | host: 'agora.anarplex.net', 18 | port: 14716, 19 | }; 20 | 21 | export const IrcServers = { 22 | primary_server, 23 | secondary_server, 24 | tertiary_server, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/WhitelistHandler.ts: -------------------------------------------------------------------------------- 1 | export interface WhitelistHandler { 2 | (nick: string): boolean; 3 | } 4 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/crypto.ts: -------------------------------------------------------------------------------- 1 | import { sha256 } from '@node-dlc/crypto'; 2 | import { IrcMessageV0 } from '@node-dlc/messaging'; 3 | import secp256k1 from 'secp256k1'; 4 | export const TOKEN_EXPIRY = 1000 * 60; 5 | export const EXPECTED_PREFIX = `dlc/v0/irc/token`; 6 | 7 | export const verifyBase64Signature = ( 8 | signature: Buffer, 9 | message: Buffer, 10 | pubkey: Buffer, 11 | ): boolean => { 12 | return secp256k1.ecdsaVerify(signature, message, pubkey); 13 | }; 14 | 15 | export const verifySignature = (ircMessage: IrcMessageV0): void => { 16 | const message = sha256(ircMessage.serializeWithoutSig()); 17 | const currentTime = Math.floor(new Date().getTime() / 1000); 18 | 19 | const validTime = 20 | currentTime - TOKEN_EXPIRY < ircMessage.timestamp && 21 | currentTime + TOKEN_EXPIRY > ircMessage.timestamp; 22 | 23 | if (!validTime) throw new Error('Invalid timestamp'); 24 | if (!verifyBase64Signature(ircMessage.signature, message, ircMessage.pubkey)) 25 | throw new Error('Invalid signature'); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/transport/lib/irc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IrcManager'; 2 | export * from './IrcOrderManager'; 3 | export * from './ChannelType'; 4 | export * from './WhitelistHandler'; 5 | export * from './Servers'; 6 | -------------------------------------------------------------------------------- /packages/transport/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/transport", 3 | "version": "0.24.1", 4 | "description": "DLC Transport", 5 | "scripts": { 6 | "start": "npm run build && node dist/index.js", 7 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --timeout 20000 --exit --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 8 | "lint": "../../node_modules/.bin/eslint --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 9 | "lint:fix": "../../node_modules/.bin/eslint --fix --ignore-path ../../.eslintignore -c ../../.eslintrc.js .", 10 | "build": "../../node_modules/.bin/tsc --project tsconfig.json", 11 | "prepublish": "npm run build" 12 | }, 13 | "keywords": [ 14 | "dlc", 15 | "transport", 16 | "irc" 17 | ], 18 | "author": "Atomic Finance ", 19 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/transport", 20 | "license": "MIT", 21 | "main": "dist/index.js", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 25 | }, 26 | "dependencies": { 27 | "@node-dlc/bitcoin": "^0.24.1", 28 | "@node-dlc/bufio": "^0.24.1", 29 | "@node-dlc/core": "^0.24.1", 30 | "@node-dlc/crypto": "^0.24.1", 31 | "@node-dlc/messaging": "^0.24.1", 32 | "@node-dlc/wire": "^0.24.1", 33 | "bitcoinjs-lib": "6.1.7", 34 | "ecpair": "^2.0.1", 35 | "irc-upd": "^0.11.0", 36 | "secp256k1": "4.0.2", 37 | "tiny-secp256k1": "^2.2.3" 38 | }, 39 | "devDependencies": { 40 | "@node-dlc/logger": "^0.24.1", 41 | "@types/node": "18.11.9", 42 | "fs": "0.0.1-security", 43 | "net": "1.0.2", 44 | "path": "0.12.7", 45 | "sinon": "10.0.0", 46 | "sinon-chai": "3.6.0", 47 | "tls": "0.0.1" 48 | }, 49 | "publishConfig": { 50 | "access": "public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/transport/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "resolveJsonModule": true 6 | }, 7 | "include": ["./lib"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/wire/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc" 4 | ], 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/wire/README.md: -------------------------------------------------------------------------------- 1 | # `@node-dlc/wire` 2 | 3 | This repository contains the wire protocol code for the Lightning Network and 4 | includes the following functionality: 5 | 6 | - Peer which manages a connection to a peer. This class allows sending and emitting message traffic. It also manages internal state for Ping/Pong message traffic. [/lib/peer.ts](lib/peer.ts) 7 | 8 | - Messages defined in BOLT #1 - Base Protocol, BOLT #2 - Peer Protocol for Channel Management, and BOLT #7 P2P Node and Channel Discovery can be found in [/lib/messages](/lib/messages). This code includes the message types and serialization and deserialization methods for each message. 9 | 10 | - P2P Node and Channel gossip management that can be found in [/lib/gossip](/lib/gossip). This code contains the [GossipManger](/lib/gossip/gossip-manager) which controls gossip for many peers. Gossip synchronization for a single peer is managed through [PeerGossipSynchronizer](/lib/gossip/peer-gossip-synchronizer). 11 | -------------------------------------------------------------------------------- /packages/wire/__fixtures__/blockhash.txt: -------------------------------------------------------------------------------- 1 | 00000000368ca807643298b36987833a726eb1e3ce6c3139fd7ff64454f03b10 2 | 000000008d8edb0f44a92eeb1d6a1ee962bfb3fe20a077397030f00e01ccf6d2 3 | 000000004436b88343c0085812e625d47e86f77a728d26081ff83f1a57f6b0e0 4 | -------------------------------------------------------------------------------- /packages/wire/__fixtures__/utxo.txt: -------------------------------------------------------------------------------- 1 | {"bestblock":"00000000000932be24286cdb97afd3bc5efb134ff1494b5023132936c51bfd62","confirmations":276288,"value":0.16777216,"scriptPubKey":{"asm":"0 fdef2b21b827959dcaf3d31f8f0f859cd81ec5d335614ed4e338cdf8ce9d6fcb","hex":"0020fdef2b21b827959dcaf3d31f8f0f859cd81ec5d335614ed4e338cdf8ce9d6fcb","reqSigs":1,"type":"witness_v0_scripthash","addresses":["tb1qlhhjkgdcy72emjhn6v0c7ru9nnvpa3wnx4s5a48r8rxl3n5adl9syhd965"]},"coinbase":false} 2 | {"bestblock":"00000000000932be24286cdb97afd3bc5efb134ff1494b5023132936c51bfd62","confirmations":276283,"value":0.16777216,"scriptPubKey":{"asm":"0 40afbae59ad7557ac65f95919f61be963a890ddb9eb9b7bbd1405b9b4ba40576","hex":"002040afbae59ad7557ac65f95919f61be963a890ddb9eb9b7bbd1405b9b4ba40576","reqSigs":1,"type":"witness_v0_scripthash","addresses":["tb1qgzhm4ev66a2h43jljkge7cd7jcagjrwmn6um0w73gpdekjayq4mqrrpvp3"]},"coinbase":false} 3 | {"bestblock":"00000000000932be24286cdb97afd3bc5efb134ff1494b5023132936c51bfd62","confirmations":276275,"value":0.10866152,"scriptPubKey":{"asm":"0 ed341bc65acb4dab8727fe1c5bfbd028f7e5f1b52700b7b5e17eae0ad30fd45f","hex":"0020ed341bc65acb4dab8727fe1c5bfbd028f7e5f1b52700b7b5e17eae0ad30fd45f","reqSigs":1,"type":"witness_v0_scripthash","addresses":["tb1qa56ph3j6edx6hpe8lcw9h77s9rm7tud4yuqt0d0p06hq45c0630slfh98w"]},"coinbase":false} 4 | -------------------------------------------------------------------------------- /packages/wire/__tests__/MessageFactory.spec.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable: no-unused-expression 2 | import { expect } from "chai"; 3 | import * as sut from "../lib/MessageFactory"; 4 | 5 | describe("MessageFactory", () => { 6 | describe(".deserialize()", () => { 7 | it("should return constructed type", () => { 8 | const input = Buffer.from("001000000000", "hex"); 9 | const result = sut.deserialize(input); 10 | expect(result).to.not.be.undefined; 11 | }); 12 | 13 | it("should not return for unkonwn types", () => { 14 | const input = Buffer.from("111100000000", "hex"); 15 | const result = sut.deserialize(input); 16 | expect(result).to.be.undefined; 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/wire/__tests__/PeerServer.spec.ts: -------------------------------------------------------------------------------- 1 | import { BitField } from "@node-dlc/common"; 2 | import * as crypto from "@node-dlc/crypto"; 3 | import { InitFeatureFlags } from "../lib/flags/InitFeatureFlags"; 4 | import { Peer } from "../lib/Peer"; 5 | import { PeerServer } from "../lib/PeerServer"; 6 | import { createFakeLogger } from "./_test-utils"; 7 | 8 | const chainHash = Buffer.alloc(32, 0xff); 9 | 10 | const serverSecret = Buffer.alloc(32, 1); 11 | const serverPubKey = crypto.getPublicKey(serverSecret, true); 12 | 13 | const clientSecret = Buffer.alloc(32, 2); 14 | const clientPubKey = crypto.getPublicKey(clientSecret, true); 15 | 16 | const localFeatures = new BitField(); 17 | 18 | function createRemotePeer() { 19 | localFeatures.set(InitFeatureFlags.optionDataLossProtectRequired); 20 | localFeatures.set(InitFeatureFlags.initialRoutingSyncOptional); 21 | return new Peer(clientSecret, localFeatures, [chainHash], createFakeLogger()); 22 | } 23 | 24 | function createServer() { 25 | const ls = Buffer.alloc(32, 1); 26 | const logger = createFakeLogger(); 27 | return new PeerServer("127.0.0.1", 10000, ls, localFeatures, [chainHash], logger); 28 | } 29 | 30 | describe("PeerServer", () => { 31 | let server: PeerServer; 32 | let client: Peer; 33 | 34 | after(() => { 35 | client.disconnect(); 36 | server.shutdown(); 37 | }); 38 | 39 | it("emits a peer when connected", done => { 40 | server = createServer(); 41 | client = createRemotePeer(); 42 | 43 | server.on("peer", () => { 44 | done(); 45 | }); 46 | 47 | server.on("listening", () => { 48 | client.connect(serverPubKey, "127.0.0.1", 10000); 49 | }); 50 | 51 | server.listen(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/wire/__tests__/ScriptUtils.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { fundingScript } from "../lib/ScriptUtils"; 3 | 4 | describe("fundingScript", () => { 5 | it("funding script 1 < 2", () => { 6 | const pk1 = Buffer.alloc(32, 1); 7 | const pk2 = Buffer.alloc(32, 2); 8 | expect(fundingScript([pk1, pk2]).toString("hex")).to.equal( 9 | "0020c90193c6382d5ce558a53ea98e5e7354155858d2d2a4f4358a8dde945361df5b", 10 | ); 11 | }); 12 | 13 | it("funding script 2 < 1", () => { 14 | const pk1 = Buffer.alloc(32, 1); 15 | const pk2 = Buffer.alloc(32, 2); 16 | expect(fundingScript([pk2, pk1]).toString("hex")).to.equal( 17 | "0020c90193c6382d5ce558a53ea98e5e7354155858d2d2a4f4358a8dde945361df5b", 18 | ); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/wire/__tests__/_test-utils.ts: -------------------------------------------------------------------------------- 1 | import { BitField } from "@node-dlc/common"; 2 | import { ILogger, Logger } from "@node-dlc/logger"; 3 | import sinon from "sinon"; 4 | import { Readable } from "stream"; 5 | import { IWireMessage } from "../lib"; 6 | import { InitFeatureFlags } from "../lib/flags/InitFeatureFlags"; 7 | 8 | export class FakePeer extends Readable { 9 | public state; 10 | public send = sinon.stub(); 11 | public sendMessage = sinon.stub(); 12 | public pubkey = Buffer.alloc(32, 1); 13 | public localChains: Buffer[] = []; 14 | public localFeatures = new BitField(); 15 | public remoteChains: Buffer[] = []; 16 | public remoteFeatures = new BitField(); 17 | 18 | public constructor() { 19 | super({ objectMode: true }); 20 | } 21 | 22 | public _read() { 23 | // 24 | } 25 | 26 | public fakeMessage(msg: IWireMessage) { 27 | this.push(msg); 28 | } 29 | } 30 | 31 | export function createFakePeer() { 32 | return new FakePeer() as any; 33 | } 34 | 35 | export function createFakeLogger(): ILogger { 36 | const fake = sinon.createStubInstance(Logger); 37 | fake.sub = createFakeLogger as any; 38 | return fake; 39 | } 40 | 41 | export function wait(ms: number): Promise { 42 | return new Promise(resolve => setTimeout(resolve, ms)); 43 | } 44 | -------------------------------------------------------------------------------- /packages/wire/__tests__/deserialize/address/ipv4StringFromBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ipv4StringFromBuffer } from "../../../lib/deserialize/address/ipv4StringFromBuffer"; 3 | 4 | const tests: Array<[string, Buffer, string]> = [ 5 | [ 6 | "localhost", 7 | Buffer.from([127,0,0,1]), 8 | "127.0.0.1", 9 | ], 10 | [ 11 | "standard address", 12 | Buffer.from([38, 87, 54, 163]), 13 | "38.87.54.163", 14 | ], 15 | [ 16 | "max address", 17 | Buffer.from("ffffffff", "hex"), 18 | "255.255.255.255", 19 | ], 20 | ]; // prettier-ignore 21 | 22 | describe("ipv4StringFromBuffer", () => { 23 | for (const [title, input, expected] of tests) { 24 | it(title, () => { 25 | const actual = ipv4StringFromBuffer(input); 26 | expect(actual).to.equal(expected); 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /packages/wire/__tests__/deserialize/address/torStringFromBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { torStringFromBuffer } from "../../../lib/deserialize/address/torStringFromBuffer"; 3 | 4 | const tests: Array<[string, Buffer, string]> = [ 5 | [ 6 | "Tor v2 address", 7 | Buffer.from("d9b547af8f8795428b8c", "hex"), 8 | "3g2upl4pq6kufc4m.onion", 9 | ], 10 | [ 11 | "Tor v3 address", 12 | Buffer.from("025b57997e7c58a0ff0a9d684f219db20835e756dea871580060d0410e257dac66ae03", "hex"), 13 | "ajnvpgl6prmkb7yktvue6im5wiedlz2w32uhcwaamdiecdrfpwwgnlqd.onion", 14 | ], 15 | ]; // prettier-ignore 16 | 17 | describe("torStringFromBuffer", () => { 18 | for (const [title, input, expected] of tests) { 19 | it(title, () => { 20 | const actual = torStringFromBuffer(input); 21 | expect(actual).to.equal(expected); 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /packages/wire/__tests__/domain/AddressIPv4.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { AddressIPv4 } from "../../lib/domain/AddressIPv4"; 3 | 4 | describe("AddressIPv4", () => { 5 | let sut: AddressIPv4; 6 | 7 | before(() => { 8 | sut = new AddressIPv4("127.0.0.1", 9735); 9 | }); 10 | 11 | it("should have type 1", () => { 12 | expect(sut.type).to.equal(1); 13 | }); 14 | 15 | describe(".toString", () => { 16 | it("should return address concatinated with port", () => { 17 | const actual = sut.toString(); 18 | const expected = "127.0.0.1:9735"; 19 | expect(actual).to.equal(expected); 20 | }); 21 | }); 22 | 23 | describe(".toJSON", () => { 24 | it("should return object", () => { 25 | const actual = sut.toJSON(); 26 | expect(actual).to.deep.equal({ 27 | network: "tcp", 28 | address: "127.0.0.1:9735", 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/wire/__tests__/domain/AddressIPv6.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { AddressIPv6 } from "../../lib/domain/AddressIPv6"; 3 | 4 | describe("AddressIPv6", () => { 5 | let sut: AddressIPv6; 6 | 7 | before(() => { 8 | sut = new AddressIPv6("::1", 9735); 9 | }); 10 | 11 | it("should have type 2", () => { 12 | expect(sut.type).to.equal(2); 13 | }); 14 | 15 | describe(".toString", () => { 16 | it("should return address concatinated with port", () => { 17 | const actual = sut.toString(); 18 | const expected = "[::1]:9735"; 19 | expect(actual).to.equal(expected); 20 | }); 21 | }); 22 | 23 | describe(".toJSON", () => { 24 | it("should return object", () => { 25 | const actual = sut.toJSON(); 26 | expect(actual).to.deep.equal({ 27 | network: "tcp", 28 | address: "[::1]:9735", 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/wire/__tests__/domain/AddressTor2.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { AddressTor2 } from "../../lib/domain/AddressTor2"; 3 | 4 | describe("AddressTor2", () => { 5 | let sut: AddressTor2; 6 | 7 | before(() => { 8 | sut = new AddressTor2("abcdefghij.onion", 9735); 9 | }); 10 | 11 | it("should have type 3", () => { 12 | expect(sut.type).to.equal(3); 13 | }); 14 | 15 | describe(".toString", () => { 16 | it("should return address concatinated with port", () => { 17 | const actual = sut.toString(); 18 | const expected = "abcdefghij.onion:9735"; 19 | expect(actual).to.equal(expected); 20 | }); 21 | }); 22 | 23 | describe(".toJSON", () => { 24 | it("should return object", () => { 25 | const actual = sut.toJSON(); 26 | expect(actual).to.deep.equal({ 27 | network: "tcp", 28 | address: "abcdefghij.onion:9735", 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/wire/__tests__/domain/AddressTor3.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { AddressTor3 } from "../../lib/domain/AddressTor3"; 3 | 4 | describe("AddressTor3", () => { 5 | let sut: AddressTor3; 6 | 7 | before(() => { 8 | sut = new AddressTor3("abcdefghijabcdefghijabcdefghij23456.onion", 9735); 9 | }); 10 | 11 | it("should have type 4", () => { 12 | expect(sut.type).to.equal(4); 13 | }); 14 | 15 | describe(".toString", () => { 16 | it("should return address concatinated with port", () => { 17 | const actual = sut.toString(); 18 | const expected = "abcdefghijabcdefghijabcdefghij23456.onion:9735"; 19 | expect(actual).to.equal(expected); 20 | }); 21 | }); 22 | 23 | describe(".toJSON", () => { 24 | it("should return object", () => { 25 | const actual = sut.toJSON(); 26 | expect(actual).to.deep.equal({ 27 | network: "tcp", 28 | address: "abcdefghijabcdefghijabcdefghij23456.onion:9735", 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/wire/__tests__/domain/checksum.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { Checksum } from "../../lib/domain/Checksum"; 3 | 4 | describe("Checksum", () => { 5 | describe(".empty()", () => { 6 | it("should return empty buffer", () => { 7 | expect(Checksum.empty().toBuffer()).to.deep.equal(Buffer.alloc(4)); 8 | }); 9 | }); 10 | 11 | describe(".equals()", () => { 12 | it("should return false when not equal", () => { 13 | const a = Checksum.fromBuffer(Buffer.from("a")); 14 | const b = Checksum.fromBuffer(Buffer.from("b")); 15 | expect(a.equals(b)).to.equal(false); 16 | }); 17 | 18 | it("should return true when equal", () => { 19 | const a = Checksum.fromBuffer(Buffer.from("a")); 20 | const b = Checksum.fromBuffer(Buffer.from("a")); 21 | expect(a.equals(b)).to.equal(true); 22 | }); 23 | }); 24 | 25 | describe(".toNumber()", () => { 26 | it("should return the number", () => { 27 | const sut = Checksum.fromBuffer(Buffer.from("a")); 28 | expect(sut.toNumber()).to.equal(3251651376); 29 | }); 30 | }); 31 | 32 | describe(".toBuffer()", () => { 33 | it("shoudl return expected buffer", () => { 34 | const sut = Checksum.fromBuffer(Buffer.from("a")); 35 | expect(sut.toBuffer().toString("hex")).to.deep.equal("c1d04330"); 36 | }); 37 | }); 38 | 39 | describe(".toString()", () => { 40 | it("shoudl return expected buffer", () => { 41 | const sut = Checksum.fromBuffer(Buffer.from("a")); 42 | expect(sut.toString()).to.deep.equal("c1d04330"); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/AnnouncementSignaturesMessage.spec.ts: -------------------------------------------------------------------------------- 1 | // TODO collect one of these and write a test against it 2 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/ErrorMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ErrorMessage } from "../../lib/messages/ErrorMessage"; 3 | 4 | describe("ErrorMessage", () => { 5 | describe(".serialize", () => { 6 | it("should serialize an error with a channelId", () => { 7 | const sut = new ErrorMessage(); 8 | sut.channelId = 1; 9 | expect(sut.serialize()).to.deep.equal(Buffer.from([0, 17, 0, 0, 0, 1, 0, 0])); 10 | }); 11 | 12 | it("should serialize with data bytes", () => { 13 | const sut = new ErrorMessage(); 14 | sut.channelId = 1; 15 | sut.data = Buffer.from([250, 250]); 16 | expect(sut.serialize()).to.deep.equal(Buffer.from([0, 17, 0, 0, 0, 1, 0, 2, 250, 250])); 17 | }); 18 | }); 19 | 20 | describe(".deserialize", () => { 21 | it("should deserialize type 17", () => { 22 | const result = ErrorMessage.deserialize( 23 | Buffer.from([0, 17, 0, 0, 0, 1, 0, 2, 250, 250]), 24 | ); 25 | expect(result.type).to.equal(17); 26 | }); 27 | 28 | it("should deserialize channelId", () => { 29 | const result = ErrorMessage.deserialize( 30 | Buffer.from([0, 17, 0, 0, 0, 1, 0, 2, 250, 250]), 31 | ); 32 | expect(result.channelId).to.equal(1); 33 | }); 34 | 35 | it("should deserialize data", () => { 36 | const result = ErrorMessage.deserialize( 37 | Buffer.from([0, 17, 0, 0, 0, 1, 0, 2, 250, 250]), 38 | ); 39 | expect(result.data).to.deep.equal(Buffer.from([250, 250])); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/FundingLockedMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { ChannelId } from "@node-dlc/common"; 2 | import { expect } from "chai"; 3 | import { FundingLockedMessage } from "../../lib/messages/FundingLockedMessage"; 4 | 5 | describe("FundingLockedMessage", () => { 6 | describe(".deserialize", () => { 7 | it("should deserialize without error", () => { 8 | const input = Buffer.from("00240000000000000000000000000000000000000000000000000000000000000000222222222222222222222222222222222222222222222222222222222222222222", "hex"); // prettier-ignore 9 | const result = FundingLockedMessage.deserialize(input); 10 | expect(result.type).to.equal(36); 11 | expect(result.channelId.toString()).to.equal("0000000000000000000000000000000000000000000000000000000000000000"); // prettier-ignore 12 | expect(result.nextPerCommitmentPoint.toString("hex")).to.equal("222222222222222222222222222222222222222222222222222222222222222222"); // prettier-ignore 13 | }); 14 | }); 15 | 16 | describe(".serialize", () => { 17 | it("should serialize a message", () => { 18 | const instance = new FundingLockedMessage(); 19 | instance.channelId = new ChannelId(Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex")); // prettier-ignore 20 | instance.nextPerCommitmentPoint = Buffer.from("222222222222222222222222222222222222222222222222222222222222222222", "hex"); // prettier-ignore 21 | 22 | const result = instance.serialize(); 23 | expect(result.toString("hex")).to.deep.equal( 24 | "00240000000000000000000000000000000000000000000000000000000000000000222222222222222222222222222222222222222222222222222222222222222222", 25 | ); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/GossipTimestampFilterMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { GossipTimestampFilterMessage } from "../../lib/messages/GossipTimestampFilterMessage"; 3 | 4 | describe("GossipTimestampFilterMessage", () => { 5 | describe("serialize", () => { 6 | it("standard message", () => { 7 | const msg = new GossipTimestampFilterMessage(); 8 | msg.chainHash = Buffer.from( 9 | "43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000", 10 | "hex", 11 | ); 12 | msg.firstTimestamp = 1578591431; 13 | msg.timestampRange = 4294967295; 14 | expect(msg.serialize().toString("hex")).to.equal( 15 | "010943497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000005e1764c7ffffffff", 16 | ); 17 | }); 18 | }); 19 | 20 | describe("deserialize", () => { 21 | it("standard message", () => { 22 | const payload = Buffer.from( 23 | "010943497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000005e1764c7ffffffff", 24 | "hex", 25 | ); 26 | const msg = GossipTimestampFilterMessage.deserialize(payload); 27 | expect(msg.chainHash.toString("hex")).to.equal( 28 | "43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000", 29 | ); 30 | expect(msg.firstTimestamp).to.equal(1578591431); 31 | expect(msg.timestampRange).to.equal(4294967295); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/PongMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { PongMessage } from "../../lib/messages/PongMessage"; 3 | 4 | describe("PongMessage", () => { 5 | describe(".serialize", () => { 6 | it("should serialize with ignored bytes", () => { 7 | const sut = new PongMessage(2); 8 | expect(sut.serialize()).to.deep.equal(Buffer.from([0, 19, 0, 2, 0, 0])); 9 | }); 10 | }); 11 | 12 | describe(".deserialize", () => { 13 | it("should deserialize type 19", () => { 14 | const result = PongMessage.deserialize(Buffer.from([0, 19, 0, 2, 0, 0])); 15 | expect(result.type).to.equal(19); 16 | }); 17 | 18 | it("should deserialize ignored", () => { 19 | const result = PongMessage.deserialize(Buffer.from([0, 19, 0, 2, 0, 0])); 20 | expect(result.ignored).to.deep.equal(Buffer.alloc(2)); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/ReplyShortChannelIdsEndMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ReplyShortChannelIdsEndMessage } from "../../lib/messages/ReplyShortChannelIdsEndMessage"; 3 | 4 | describe("ReplyShortChannelIdsEndMessage", () => { 5 | describe(".deserialize", () => { 6 | it("standard message", () => { 7 | const payload = Buffer.from("010643497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000001", "hex"); // prettier-ignore 8 | // reply_short_channel_ids_end 9 | // 010643497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000001 10 | // 0106 - type 262 11 | // 43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000 - chain_hash 12 | // 01 - complete true 13 | 14 | const msg = ReplyShortChannelIdsEndMessage.deserialize(payload); 15 | expect(msg.type).to.equal(262); 16 | expect(msg.chainHash.toString("hex")).to.equal("43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000"); // prettier-ignore 17 | // tslint:disable-next-line: no-unused-expression 18 | expect(msg.complete).to.be.true; 19 | }); 20 | }); 21 | 22 | describe(".serialize", () => { 23 | describe("standard message", () => { 24 | const msg = new ReplyShortChannelIdsEndMessage(); 25 | msg.chainHash = Buffer.from( 26 | "43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000", 27 | "hex", 28 | ); 29 | msg.complete = true; 30 | expect(msg.serialize().toString("hex")).to.equal( 31 | "010643497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea33090000000001", 32 | ); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/wire/__tests__/messages/ShutdownChannelMessage.spec.ts: -------------------------------------------------------------------------------- 1 | import { ChannelId } from "@node-dlc/common"; 2 | import { expect } from "chai"; 3 | import { ShutdownMessage } from "../../lib/messages/ShutdownMessage"; 4 | 5 | describe("ShutdownChannelMessage", () => { 6 | describe(".deserialize", () => { 7 | it("should deserialize without error", () => { 8 | const input = Buffer.from( 9 | "0026"+ // type 10 | "0000000000000000000000000000000000000000000000000000000000000000" + // Channel ID 11 | "0015" + //len 12 | "00a41a8527eab06efc0a8df57045d247784a071e23" //p2wpkh 13 | ,"hex"); // prettier-ignore 14 | const result = ShutdownMessage.deserialize(input); 15 | expect(result.type).to.equal(38); 16 | expect(result.channelId.toString()).to.equal("0000000000000000000000000000000000000000000000000000000000000000"); // prettier-ignore 17 | expect(result.scriptPubKey.toString("hex")).to.equal("00a41a8527eab06efc0a8df57045d247784a071e23"); // prettier-ignore 18 | }); 19 | }); 20 | describe(".serialize", () => { 21 | it("should serialize a message", () => { 22 | const instance = new ShutdownMessage(); 23 | instance.channelId = new ChannelId(Buffer.from("0000000000000000000000000000000000000000000000000000000000000000", "hex")); // prettier-ignore 24 | instance.scriptPubKey = Buffer.from("00a41a8527eab06efc0a8df57045d247784a071e23", "hex"); // prettier-ignore 25 | 26 | const result = instance.serialize(); 27 | expect(result.toString("hex")).to.equal( 28 | "0026" + 29 | "0000000000000000000000000000000000000000000000000000000000000000" + 30 | "0015" + 31 | "00a41a8527eab06efc0a8df57045d247784a071e23", 32 | ); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/wire/__tests__/serialize/address/ipv4StringToBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { ipv4StringToBuffer } from "../../../lib/serialize/address/ipv4StringToBuffer"; 3 | 4 | const tests: Array<[string, string, Buffer]> = [ 5 | ["localhost", "127.0.0.1", Buffer.from([127, 0, 0, 1])], 6 | ["standard address", "38.87.54.163", Buffer.from([38, 87, 54, 163])], 7 | ["max address", "255.255.255.255", Buffer.from("ffffffff", "hex")], 8 | ]; 9 | 10 | describe("ipv4StringFromBuffer", () => { 11 | for (const [title, input, expected] of tests) { 12 | it(title, () => { 13 | const actual = ipv4StringToBuffer(input); 14 | expect(actual).to.deep.equal(expected); 15 | }); 16 | } 17 | }); 18 | -------------------------------------------------------------------------------- /packages/wire/__tests__/serialize/address/serializeAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { serializeAddress as sut } from "../../../lib/serialize/address/serializeAddress"; 3 | 4 | import { Address } from "../../../lib"; 5 | import { AddressIPv4 } from "../../../lib/domain/AddressIPv4"; 6 | import { AddressIPv6 } from "../../../lib/domain/AddressIPv6"; 7 | import { AddressTor2 } from "../../../lib/domain/AddressTor2"; 8 | import { AddressTor3 } from "../../../lib/domain/AddressTor3"; 9 | 10 | const tests: Array<[string, Address, Buffer]> = [ 11 | [ 12 | "IPv4 loopback address", 13 | new AddressIPv4("127.0.0.1", 9735), 14 | Buffer.from([1, 127, 0, 0, 1, 38, 7]), 15 | ], 16 | [ 17 | "IPv4 address", 18 | new AddressIPv4("38.87.54.163", 9735), 19 | Buffer.from([1, 38, 87, 54, 163, 38, 7]), 20 | ], 21 | [ 22 | "IPv6 loopback address", 23 | new AddressIPv6("::1", 9735), 24 | Buffer.from("02000000000000000000000000000000012607", "hex"), 25 | ], 26 | [ 27 | "IPv6 address", 28 | new AddressIPv6("2604:a880:2:d0::219c:c001", 9735), 29 | Buffer.from("022604a880000200d000000000219cc0012607", "hex"), 30 | ], 31 | [ 32 | "Tor 2 address", 33 | new AddressTor2("qxgtzlxgebngzqtg.onion", 9735), 34 | Buffer.from("0385cd3caee6205a6cc2662607", "hex"), 35 | ], 36 | [ 37 | "Tor 3 address", 38 | new AddressTor3("ueo7nndq5jnsc3gg3fpnrj3v6ceedvrej5qr5lt6ppvrtd7xxk64qiid.onion", 9735), 39 | Buffer.from("04a11df6b470ea5b216cc6d95ed8a775f08841d6244f611eae7e7beb198ff7babdc821032607", "hex"), 40 | ], 41 | ]; // prettier-ignore 42 | 43 | describe("serializeAddress", () => { 44 | for (const [title, input, expected] of tests) { 45 | it(title, () => { 46 | const actual = sut(input); 47 | expect(actual).to.deep.equal(expected); 48 | }); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /packages/wire/__tests__/serialize/address/torStringToBuffer.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { torStringToBuffer } from "../../../lib/serialize/address/torStringToBuffer"; 3 | 4 | const tests: Array<[string, string, Buffer]> = [ 5 | [ 6 | "Tor v2 address", 7 | "3g2upl4pq6kufc4m.onion", 8 | Buffer.from("d9b547af8f8795428b8c", "hex"), 9 | ], 10 | [ 11 | "Tor v3 address", 12 | "ajnvpgl6prmkb7yktvue6im5wiedlz2w32uhcwaamdiecdrfpwwgnlqd.onion", 13 | Buffer.from("025b57997e7c58a0ff0a9d684f219db20835e756dea871580060d0410e257dac66ae03", "hex"), 14 | ], 15 | ]; // prettier-ignore 16 | 17 | describe("torStringToBuffer", () => { 18 | for (const [title, input, expected] of tests) { 19 | it(title, () => { 20 | const actual = torStringToBuffer(input); 21 | expect(actual).to.deep.equal(expected); 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /packages/wire/lib/Constants.ts: -------------------------------------------------------------------------------- 1 | export const PONG_BYTE_THRESHOLD = 65532; 2 | -------------------------------------------------------------------------------- /packages/wire/lib/MessageType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defined in BOLT01 3 | */ 4 | export enum MessageType { 5 | // Setup and Control (0 - 31) 6 | Init = 16, 7 | Error = 17, 8 | Ping = 18, 9 | Pong = 19, 10 | 11 | // Channel (32-127) 12 | OpenChannel = 32, 13 | AcceptChannel = 33, 14 | FundingCreated = 34, 15 | FundingSigned = 35, 16 | FundingLocked = 36, 17 | Shutdown = 38, 18 | ClosingSigned = 39, 19 | 20 | // Commitment (128-255) 21 | // 22 | 23 | // Routing (256-511) 24 | ChannelAnnouncement = 256, 25 | NodeAnnouncement = 257, 26 | ChannelUpdate = 258, 27 | AnnouncementSignatures = 259, 28 | 29 | QueryShortChannelIds = 261, 30 | ReplyShortChannelIdsEnd = 262, 31 | 32 | QueryChannelRange = 263, 33 | ReplyChannelRange = 264, 34 | 35 | GossipTimestampFilter = 265, 36 | } 37 | -------------------------------------------------------------------------------- /packages/wire/lib/PeerServer.ts: -------------------------------------------------------------------------------- 1 | import { BitField } from "@node-dlc/common"; 2 | import { ILogger } from "@node-dlc/logger"; 3 | import { NoiseSocket } from "@node-dlc/noise"; 4 | import { NoiseServer } from "@node-dlc/noise"; 5 | import { EventEmitter } from "events"; 6 | import { InitFeatureFlags } from "./flags/InitFeatureFlags"; 7 | import { Peer } from "./Peer"; 8 | 9 | export class PeerServer extends EventEmitter { 10 | protected _server: NoiseServer; 11 | 12 | constructor( 13 | readonly host: string, 14 | readonly port: number, 15 | readonly localSecret: Buffer, 16 | readonly localFeatures: BitField, 17 | readonly localChains: Buffer[], 18 | readonly logger: ILogger, 19 | ) { 20 | super(); 21 | this._server = new NoiseServer({ ls: localSecret }, this._onSocket.bind(this)); 22 | this._server.on("listening", () => this.emit("listening")); 23 | } 24 | 25 | /** 26 | * Starts the peer manager listening 27 | * @param host 28 | * @param port 29 | */ 30 | public listen() { 31 | this._server.listen({ host: this.host, port: this.port }); 32 | } 33 | 34 | /** 35 | * Shuts down the server 36 | */ 37 | public shutdown() { 38 | this._server.close(); 39 | } 40 | 41 | /** 42 | * Handles when a socket connects to us 43 | * @param socket 44 | */ 45 | protected _onSocket(socket: NoiseSocket) { 46 | this.logger.info("peer connected"); 47 | const peer = new Peer(this.localSecret, this.localFeatures, this.localChains, this.logger); 48 | peer.attach(socket); 49 | this.emit("peer", peer); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/wire/lib/PeerState.ts: -------------------------------------------------------------------------------- 1 | export enum PeerState { 2 | Disconnected = 0, 3 | AwaitingPeerInit = 1, 4 | Ready = 100, 5 | Disconnecting = 1000, 6 | } 7 | -------------------------------------------------------------------------------- /packages/wire/lib/WireError.ts: -------------------------------------------------------------------------------- 1 | export enum WireErrorCode { 2 | nodeAnnSigFailed = 1, 3 | chanAnnSigFailed = 2, 4 | chanUpdSigFailed = 3, 5 | chanBadBlockHash = 4, 6 | chanBadBlock = 5, 7 | chanAnnBadTx = 6, 8 | chanUtxoSpent = 7, 9 | chanBadScript = 8, 10 | gossipManagerNotStarted = 101, 11 | } 12 | 13 | const errorCodeStrings = { 14 | 1: "node_ann_sig_failed", 15 | 2: "chan_ann_sig_failed", 16 | 3: "chan_upd_sig_failed", 17 | 4: "chan_bad_block_hash", 18 | 5: "chan_bad_block", 19 | 6: "chan_bad_tx", 20 | 7: "chan_utxo_spent", 21 | 8: "chan_bad_script", 22 | 101: "gossip_manager_not_started", 23 | }; 24 | 25 | /** 26 | * Creates an error for a wire operation and captures relevant that 27 | * caused the error to be emitted or thrown. 28 | */ 29 | export class WireError extends Error { 30 | public area: string; 31 | public code: WireErrorCode; 32 | public data: any; 33 | 34 | constructor(code: WireErrorCode, data?: any) { 35 | const msg = `${errorCodeStrings[code]}`; 36 | super(msg); 37 | 38 | this.area = "wire"; 39 | this.code = code; 40 | this.data = data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/deserializeAddress.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | import { Address } from '../../domain/Address'; 4 | import { AddressType } from '../../domain/AddressType'; 5 | import { deserializeIPv4 } from './deserializeIPv4'; 6 | import { deserializeIPv6 } from './deserializeIPv6'; 7 | import { deserializeTor2 } from './deserializeTor2'; 8 | import { deserializeTor3 } from './deserializeTor3'; 9 | 10 | /** 11 | * Deserializes an address based on the type and returns 12 | * an instance of Address as a polymorphic type. 13 | */ 14 | export function deserializeAddress( 15 | type: AddressType, 16 | reader: BufferReader, 17 | ): Address { 18 | switch (type) { 19 | case AddressType.IPv4: 20 | return deserializeIPv4(reader); 21 | case AddressType.IPv6: 22 | return deserializeIPv6(reader); 23 | case AddressType.TOR2: 24 | return deserializeTor2(reader); 25 | case AddressType.TOR3: 26 | return deserializeTor3(reader); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/deserializeIPv4.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | import { AddressIPv4 } from '../../domain/AddressIPv4'; 4 | import { ipv4StringFromBuffer } from './ipv4StringFromBuffer'; 5 | 6 | /** 7 | * Deserializes an IPv4 address from a reader and 8 | * returns an instance of an IPv4 Address. 9 | */ 10 | export function deserializeIPv4(reader: BufferReader): AddressIPv4 { 11 | const hostBytes = reader.readBytes(4); 12 | const port = reader.readUInt16BE(); 13 | const host = ipv4StringFromBuffer(hostBytes); 14 | return new AddressIPv4(host, port); 15 | } 16 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/deserializeIPv6.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | import { AddressIPv6 } from '../../domain/AddressIPv6'; 4 | import { ipv6StringFromBuffer } from './ipv6StringFromBuffer'; 5 | 6 | /** 7 | * Deserializes an IPv6 address from a reader and 8 | * returns an instance of an IPv6 Address. 9 | */ 10 | export function deserializeIPv6(reader: BufferReader): AddressIPv6 { 11 | const hostBytes = reader.readBytes(16); 12 | const port = reader.readUInt16BE(); 13 | const host = ipv6StringFromBuffer(hostBytes); 14 | return new AddressIPv6(host, port); 15 | } 16 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/deserializeTor2.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | import { AddressTor2 } from '../../domain/AddressTor2'; 4 | import { torStringFromBuffer } from './torStringFromBuffer'; 5 | 6 | /** 7 | * Deserializes a TOR v2 address from a reader and 8 | * returns an instance of a AddressTor2. 9 | */ 10 | export function deserializeTor2(reader: BufferReader): AddressTor2 { 11 | const hostBytes = reader.readBytes(10); 12 | const port = reader.readUInt16BE(); 13 | const host = torStringFromBuffer(hostBytes); 14 | return new AddressTor2(host, port); 15 | } 16 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/deserializeTor3.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | import { AddressTor3 } from '../../domain/AddressTor3'; 4 | import { torStringFromBuffer } from './torStringFromBuffer'; 5 | 6 | /** 7 | * Deserializes a TOR v3 address from a reader and 8 | * returns an instance of a AddressTor2. 9 | */ 10 | export function deserializeTor3(reader: BufferReader): AddressTor3 { 11 | const hostBytes = reader.readBytes(35); 12 | const port = reader.readUInt16BE(); 13 | const host = torStringFromBuffer(hostBytes); 14 | return new AddressTor3(host, port); 15 | } 16 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/ipv4StringFromBuffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts an IPv4 address into string notation 3 | * of the format x.x.x.x where each x is an 8-bit integer. 4 | */ 5 | export function ipv4StringFromBuffer(bytes: Buffer): string { 6 | return bytes.join('.'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/wire/lib/deserialize/address/torStringFromBuffer.ts: -------------------------------------------------------------------------------- 1 | import { Base32 } from '@node-dlc/common'; 2 | 3 | /** 4 | * Converts a Buffer into a TOR address including the .onion suffix 5 | */ 6 | export function torStringFromBuffer(buffer: Buffer): string { 7 | return Base32.encode(buffer).toLowerCase() + '.onion'; 8 | } 9 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/Address.ts: -------------------------------------------------------------------------------- 1 | import { AddressJson } from './AddressJson'; 2 | import { AddressType } from './AddressType'; 3 | import { NetworkType } from './NetworkType'; 4 | 5 | export abstract class Address { 6 | /** 7 | * String notation representation of the host 8 | */ 9 | public host: string; 10 | 11 | /** 12 | * Port number 13 | */ 14 | public port: number; 15 | 16 | /** 17 | * Base class representing a network address 18 | */ 19 | constructor(host: string, port: number) { 20 | this.host = host; 21 | this.port = port; 22 | } 23 | 24 | /** 25 | * Type of connection 26 | */ 27 | public get type(): AddressType { 28 | throw new Error('Not implemented'); 29 | } 30 | 31 | public toString() { 32 | return `${this.host}:${this.port}`; 33 | } 34 | 35 | public toJSON(): AddressJson { 36 | return { 37 | network: NetworkType.TCP, 38 | address: this.toString(), 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressIPv4.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './Address'; 2 | import { AddressType } from './AddressType'; 3 | 4 | export class AddressIPv4 extends Address { 5 | /** 6 | * Represents an IPv4 address with the host and port. 7 | */ 8 | constructor(host: string, port: number) { 9 | super(host, port); 10 | } 11 | 12 | public get type(): AddressType { 13 | return AddressType.IPv4; 14 | } 15 | 16 | public toString() { 17 | return `${this.host}:${this.port}`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressIPv6.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './Address'; 2 | import { AddressType } from './AddressType'; 3 | 4 | export class AddressIPv6 extends Address { 5 | /** 6 | * Represents an IPv6 address with the host and port. 7 | */ 8 | constructor(host: string, port: number) { 9 | super(host, port); 10 | } 11 | 12 | public get type(): AddressType { 13 | return AddressType.IPv6; 14 | } 15 | 16 | public toString() { 17 | return `[${this.host}]:${this.port}`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressJson.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from './NetworkType'; 2 | 3 | export type AddressJson = { 4 | network: NetworkType; 5 | address: string; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressTor2.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './Address'; 2 | import { AddressType } from './AddressType'; 3 | 4 | export class AddressTor2 extends Address { 5 | /** 6 | * Represents an TOR v2 address with the host and port. TOR v2 7 | * addresses are an 80-bit hash represented as base32 encoding 8 | * that is 16 characters in length 9 | */ 10 | constructor(host: string, port: number) { 11 | super(host, port); 12 | } 13 | 14 | public get type() { 15 | return AddressType.TOR2; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressTor3.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './Address'; 2 | import { AddressType } from './AddressType'; 3 | 4 | export class AddressTor3 extends Address { 5 | /** 6 | * Represents an Tor v3 address with the host and port. Tor v3 7 | * addresses are base32 encoded ed25519 public key and are 8 | * 56-characters in length. 9 | */ 10 | constructor(host: string, port: number) { 11 | super(host, port); 12 | } 13 | 14 | public get type(): AddressType { 15 | return AddressType.TOR3; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/AddressType.ts: -------------------------------------------------------------------------------- 1 | export enum AddressType { 2 | IPv4 = 1, 3 | IPv6 = 2, 4 | TOR2 = 3, 5 | TOR3 = 4, 6 | } 7 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/Checksum.ts: -------------------------------------------------------------------------------- 1 | import { crc32c } from '@node-dlc/checksum'; 2 | 3 | /** 4 | * CRC32C checksum for the provided value 5 | */ 6 | export class Checksum { 7 | public static fromBuffer(buf: Buffer): Checksum { 8 | return new Checksum(crc32c(buf)); 9 | } 10 | 11 | public static empty(): Checksum { 12 | return new Checksum(0); 13 | } 14 | 15 | private _checksum: number; 16 | 17 | private constructor(checksum: number) { 18 | this._checksum = checksum; 19 | } 20 | 21 | public equals(other: Checksum): boolean { 22 | return this._checksum === other._checksum; 23 | } 24 | 25 | public toNumber(): number { 26 | return this._checksum; 27 | } 28 | 29 | public toBuffer(): Buffer { 30 | const buf = Buffer.alloc(4); 31 | buf.writeUInt32BE(this._checksum, 0); 32 | return buf; 33 | } 34 | 35 | public toString(): string { 36 | return this._checksum.toString(16); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/wire/lib/domain/NetworkType.ts: -------------------------------------------------------------------------------- 1 | export enum NetworkType { 2 | TCP = 'tcp', 3 | } 4 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/ChanneUpdateChannelFlags.ts: -------------------------------------------------------------------------------- 1 | export enum ChannelUpdateChannelFlags { 2 | direction = 0, 3 | disabled = 1, 4 | } 5 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/ChannelFeatureFlags.ts: -------------------------------------------------------------------------------- 1 | export enum ChannelFeatureFlags {} 2 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/ChannelUpdateMessageFlags.ts: -------------------------------------------------------------------------------- 1 | export enum ChannelUpdateMessageFlags { 2 | htlcMaximumMsat = 0, 3 | } 4 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/NodeFeatureFlags.ts: -------------------------------------------------------------------------------- 1 | export enum NodeFeatureFlags { 2 | optionDataLossProtectRequired = 0, 3 | optionDataLossProtectOptional = 1, 4 | 5 | optionUpfrontShutdownScriptRequired = 4, 6 | optionUpfrontShutdownScriptOptional = 5, 7 | 8 | gossipQueriesRequired = 6, 9 | gossipQueriesOptional = 7, 10 | 11 | optionVarOptionOptinRequired = 8, 12 | optionVarOptionOptinOptional = 9, 13 | 14 | gossipQueriesExRequired = 10, 15 | gossipQueriesExOptional = 11, 16 | 17 | optionStaticRemoteKeyRequired = 12, 18 | optionStaticRemoteKeyOptional = 13, 19 | 20 | paymentSecretRequired = 14, 21 | paymentSecretOptional = 15, 22 | 23 | basicMppRequired = 16, 24 | basicMppOptional = 17, 25 | 26 | optionSupportLargeChannelRequired = 18, 27 | optionSupportLargeChannelOptional = 19, 28 | } 29 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/OpenChannelFlags.ts: -------------------------------------------------------------------------------- 1 | export enum OpenChannelFlags { 2 | AnnounceChannel = 0, 3 | } 4 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/QueryChannelRangeFlags.ts: -------------------------------------------------------------------------------- 1 | export enum QueryChannelRangeFlags { 2 | timestamps = 0, 3 | checksums = 1, 4 | } 5 | -------------------------------------------------------------------------------- /packages/wire/lib/flags/QueryScidFlags.ts: -------------------------------------------------------------------------------- 1 | export enum QueryScidFlags { 2 | ChannelAnnouncements = 0, 3 | ChannelUpdatesNode1 = 1, 4 | ChannelUpdatesNode2 = 2, 5 | NodeAnnouncementsNode1 = 3, 6 | NodeAnnouncementsNode2 = 4, 7 | } 8 | -------------------------------------------------------------------------------- /packages/wire/lib/gossip/GossipEmitter.ts: -------------------------------------------------------------------------------- 1 | import { IWireMessage } from '../messages/IWireMessage'; 2 | 3 | export interface IGossipEmitter { 4 | on(event: 'message', fn: (msg: IWireMessage) => void); 5 | } 6 | -------------------------------------------------------------------------------- /packages/wire/lib/gossip/GossipError.ts: -------------------------------------------------------------------------------- 1 | import { IWireMessage } from '../messages/IWireMessage'; 2 | 3 | export enum GossipErrorCode { 4 | Unknown = 0, 5 | PeerNotReady, 6 | ReplyChannelRangeNoInformation, 7 | ReplyChannelsNoInfo, 8 | } 9 | 10 | export class GossipError extends Error { 11 | public code: GossipErrorCode; 12 | public wireMessage: IWireMessage; 13 | 14 | constructor(code: GossipErrorCode, wireMessage?: IWireMessage) { 15 | let message = 'Unknown gossip error'; 16 | switch (code) { 17 | case GossipErrorCode.ReplyChannelRangeNoInformation: 18 | message = 'reply_channel_range had no information'; 19 | break; 20 | case GossipErrorCode.ReplyChannelsNoInfo: 21 | message = 'reply_short_channel_ids_end had no information'; 22 | break; 23 | } 24 | super(message); 25 | this.code = code; 26 | this.wireMessage = wireMessage; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/wire/lib/gossip/GossipStore.ts: -------------------------------------------------------------------------------- 1 | import { OutPoint } from '@node-dlc/bitcoin'; 2 | import { ShortChannelId } from '@node-dlc/common'; 3 | 4 | import { ChannelAnnouncementMessage } from '../messages/ChannelAnnouncementMessage'; 5 | import { ChannelUpdateMessage } from '../messages/ChannelUpdateMessage'; 6 | import { NodeAnnouncementMessage } from '../messages/NodeAnnouncementMessage'; 7 | 8 | /** 9 | * Interface for storing, finding, and deleting gossip messages. 10 | */ 11 | export interface IGossipStore { 12 | findChannelAnnouncemnts(): Promise; 13 | findChannelsForNode(nodeId: Buffer): Promise; 14 | findNodeAnnouncement(nodeId: Buffer): Promise; 15 | findNodeAnnouncements(): Promise; 16 | findChannelAnnouncement( 17 | scid: ShortChannelId, 18 | ): Promise; 19 | findChannelAnnouncementByOutpoint( 20 | outpoint: OutPoint, 21 | ): Promise; 22 | findChannelUpdate( 23 | scid: ShortChannelId, 24 | dir: number, 25 | ): Promise; 26 | saveChannelAnnouncement(msg: ChannelAnnouncementMessage): Promise; 27 | saveChannelUpdate(msg: ChannelUpdateMessage): Promise; 28 | saveNodeAnnouncement(msg: NodeAnnouncementMessage): Promise; 29 | deleteChannelAnnouncement(scid: ShortChannelId): Promise; 30 | deleteChannelUpdate(scid: ShortChannelId, dir: number): Promise; 31 | deleteNodeAnnouncement(nodeId: Buffer): Promise; 32 | } 33 | -------------------------------------------------------------------------------- /packages/wire/lib/gossip/IGossipFilterChainClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The interface required by the GossipFilter to perform on-chain validation checks 3 | * of messages. A simplified interface is required by the GossipFilter. The actual 4 | * chain client implementation may offer a broader set of feautures. Additionally, 5 | * an adapter could be constructred to make an chain client conform to that required 6 | * by the GossipFilter. 7 | */ 8 | export interface IGossipFilterChainClient { 9 | getBlockHash(height: number): Promise; 10 | getBlock(hash: string): Promise; 11 | getUtxo(txId: string, voutIdx: number): Promise; 12 | waitForSync(): Promise; 13 | } 14 | 15 | export type HasTxStrings = { 16 | tx: string[]; 17 | }; 18 | 19 | export type HasScriptPubKey = { 20 | scriptPubKey: HasHex; 21 | }; 22 | 23 | export type HasValue = { 24 | value: number; 25 | }; 26 | 27 | export type HasHex = { 28 | hex: string; 29 | }; 30 | -------------------------------------------------------------------------------- /packages/wire/lib/messages/GossipTimestampFilterMessage.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { MessageType } from '../MessageType'; 4 | import { IWireMessage } from './IWireMessage'; 5 | 6 | export class GossipTimestampFilterMessage implements IWireMessage { 7 | public static deserialize(payload: Buffer): GossipTimestampFilterMessage { 8 | const instance = new GossipTimestampFilterMessage(); 9 | const reader = new BufferReader(payload); 10 | reader.readUInt16BE(); // read off type 11 | instance.chainHash = reader.readBytes(32); 12 | instance.firstTimestamp = reader.readUInt32BE(); 13 | instance.timestampRange = reader.readUInt32BE(); 14 | return instance; 15 | } 16 | 17 | public type: MessageType = MessageType.GossipTimestampFilter; 18 | public chainHash: Buffer; 19 | public firstTimestamp: number; 20 | public timestampRange: number; 21 | 22 | public serialize(): Buffer { 23 | const len = 24 | 2 + // type 25 | 32 + // chain_hash 26 | 4 + // first_timestamp 27 | 4; // timestamp_range 28 | 29 | const writer = new BufferWriter(Buffer.alloc(len)); 30 | 31 | writer.writeUInt16BE(this.type); 32 | writer.writeBytes(this.chainHash); 33 | writer.writeUInt32BE(this.firstTimestamp); 34 | writer.writeUInt32BE(this.timestampRange); 35 | return writer.toBuffer(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/wire/lib/messages/IWireMessage.ts: -------------------------------------------------------------------------------- 1 | import { MessageType } from '../MessageType'; 2 | 3 | export interface IWireMessage { 4 | type: MessageType; 5 | serialize(): Buffer; 6 | } 7 | -------------------------------------------------------------------------------- /packages/wire/lib/messages/ReplyShortChannelIdsEndMessage.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader, BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { MessageType } from '../MessageType'; 4 | import { IWireMessage } from './IWireMessage'; 5 | 6 | export class ReplyShortChannelIdsEndMessage implements IWireMessage { 7 | public static deserialize(payload: Buffer): ReplyShortChannelIdsEndMessage { 8 | const instance = new ReplyShortChannelIdsEndMessage(); 9 | const reader = new BufferReader(payload); 10 | 11 | // read type bytes 12 | reader.readUInt16BE(); 13 | 14 | instance.chainHash = reader.readBytes(32); 15 | instance.complete = reader.readUInt8() === 1; 16 | 17 | return instance; 18 | } 19 | 20 | public type: MessageType = MessageType.ReplyShortChannelIdsEnd; 21 | public chainHash: Buffer; 22 | public complete: boolean; 23 | 24 | public serialize(): Buffer { 25 | const len = 26 | 2 + // type 27 | 32 + // chain_hash 28 | 1; // complete 29 | 30 | const writer = new BufferWriter(Buffer.alloc(len)); 31 | writer.writeUInt16BE(this.type); 32 | writer.writeBytes(this.chainHash); 33 | writer.writeUInt8(this.complete ? 1 : 0); 34 | return writer.toBuffer(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/Encoder.ts: -------------------------------------------------------------------------------- 1 | import { EncodingType } from './EncodingType'; 2 | import { ZlibEncoder } from './ZlibEncoder'; 3 | 4 | export class Encoder { 5 | public encode(encoding: EncodingType, data: Buffer): Buffer { 6 | let encoded: Buffer; 7 | switch (encoding) { 8 | case EncodingType.Raw: 9 | encoded = data; 10 | break; 11 | case EncodingType.ZlibDeflate: 12 | encoded = new ZlibEncoder().encode(data); 13 | break; 14 | default: 15 | throw new Error('Unknown encoding type'); 16 | } 17 | return Buffer.concat([Buffer.from([encoding]), encoded]); 18 | } 19 | 20 | public decode(buffer: Buffer): Buffer { 21 | const encoding = buffer.readUInt8(0); 22 | const raw = buffer.slice(1); 23 | switch (encoding) { 24 | case EncodingType.Raw: 25 | return raw; 26 | case EncodingType.ZlibDeflate: 27 | return new ZlibEncoder().decode(raw); 28 | default: 29 | throw new Error('Unknown encoding type'); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/EncodingType.ts: -------------------------------------------------------------------------------- 1 | export enum EncodingType { 2 | Raw = 0, 3 | ZlibDeflate = 1, 4 | } 5 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/ZlibEncoder.ts: -------------------------------------------------------------------------------- 1 | import zlib from 'zlib'; 2 | 3 | export class ZlibEncoder { 4 | public encode(payload: Buffer): Buffer { 5 | return zlib.deflateSync(payload); 6 | } 7 | 8 | public decode(payload: Buffer): Buffer { 9 | return zlib.inflateSync(payload); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/ipv4StringToBuffer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts an IPv4 address in string notation to the 3 | * byte representation. 4 | */ 5 | export function ipv4StringToBuffer(host: string): Buffer { 6 | const parts = host.split('.'); 7 | const result = Buffer.alloc(4); 8 | for (let i = 0; i < 4; i++) { 9 | result[i] = parseInt(parts[i]); 10 | } 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/ipv6StringToBuffer.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter } from '@node-dlc/bufio'; 2 | 3 | /** 4 | * Converts an IPv6 address in string notation to the 5 | * byte representation. 6 | */ 7 | export function ipv6StringToBuffer(host: string): Buffer { 8 | // replace start or end expansion with single part to retain correct 9 | // number of parts (8) that are used in the remainder of the logic. 10 | // ie: ::1:2:3:4:5:6:7 would split to ['','',1,2,3,4,5,6,7] and 11 | // result in overflows 12 | if (host.startsWith('::')) host = host.substr(1); 13 | else if (host.endsWith('::')) host = host.substr(0, host.length - 1); 14 | 15 | const parts = host.split(':'); 16 | 17 | const writer = new BufferWriter(Buffer.alloc(16)); 18 | 19 | const expandBy = 8 - parts.length; 20 | let needsExpansion = expandBy > 0; 21 | 22 | for (const part of parts) { 23 | if (needsExpansion && part === '') { 24 | const b = Buffer.alloc((expandBy + 1) * 2); 25 | writer.writeBytes(b); 26 | needsExpansion = false; 27 | } else { 28 | const b = Buffer.from(expandZeros(part), 'hex'); 29 | writer.writeBytes(b); 30 | } 31 | } 32 | 33 | return writer.toBuffer(); 34 | } 35 | 36 | function expandZeros(part: string): string { 37 | return part.padStart(4, '0'); 38 | } 39 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/serializeAddress.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../domain/Address'; 2 | import { AddressType } from '../../domain/AddressType'; 3 | import { serializeIPv4 } from './serializeIPv4'; 4 | import { serializeIPv6 } from './serializeIPv6'; 5 | import { serializeTor2 } from './serializeTor2'; 6 | import { serializeTor3 } from './serializeTor3'; 7 | 8 | /** 9 | * Serializes an address into a Buffer that can be transmitted 10 | * over the wire 11 | */ 12 | export function serializeAddress(address: Address): Buffer { 13 | switch (address.type) { 14 | case AddressType.IPv4: 15 | return serializeIPv4(address); 16 | case AddressType.IPv6: 17 | return serializeIPv6(address); 18 | case AddressType.TOR2: 19 | return serializeTor2(address); 20 | case AddressType.TOR3: 21 | return serializeTor3(address); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/serializeIPv4.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { AddressIPv4 } from '../../domain/AddressIPv4'; 4 | import { ipv4StringToBuffer } from './ipv4StringToBuffer'; 5 | 6 | /** 7 | * Serializes an IPv4 address in a Buffer that can be sent 8 | * over the wire. 9 | */ 10 | export function serializeIPv4(address: AddressIPv4): Buffer { 11 | const writer = new BufferWriter(Buffer.alloc(7)); 12 | 13 | const hostBytes = ipv4StringToBuffer(address.host); 14 | 15 | writer.writeUInt8(address.type); 16 | writer.writeBytes(hostBytes); 17 | writer.writeUInt16BE(address.port); 18 | 19 | return writer.toBuffer(); 20 | } 21 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/serializeIPv6.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { AddressIPv6 } from '../../domain/AddressIPv6'; 4 | import { ipv6StringToBuffer } from './ipv6StringToBuffer'; 5 | 6 | /** 7 | * Serializes an IPv6 address in a Buffer that can be sent 8 | * over the wire. 9 | */ 10 | export function serializeIPv6(address: AddressIPv6): Buffer { 11 | const writer = new BufferWriter(Buffer.alloc(19)); 12 | 13 | const hostBytes = ipv6StringToBuffer(address.host); 14 | 15 | writer.writeUInt8(address.type); 16 | writer.writeBytes(hostBytes); 17 | writer.writeUInt16BE(address.port); 18 | 19 | return writer.toBuffer(); 20 | } 21 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/serializeTor2.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { AddressTor2 } from '../../domain/AddressTor2'; 4 | import { torStringToBuffer } from './torStringToBuffer'; 5 | 6 | /** 7 | * Serializes a Tor v2 address in a Buffer that can be sent 8 | * over the wire. 9 | */ 10 | export function serializeTor2(address: AddressTor2): Buffer { 11 | const writer = new BufferWriter(Buffer.alloc(13)); 12 | 13 | const hostBytes = torStringToBuffer(address.host); 14 | 15 | writer.writeUInt8(address.type); 16 | writer.writeBytes(hostBytes); 17 | writer.writeUInt16BE(address.port); 18 | 19 | return writer.toBuffer(); 20 | } 21 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/serializeTor3.ts: -------------------------------------------------------------------------------- 1 | import { BufferWriter } from '@node-dlc/bufio'; 2 | 3 | import { AddressTor3 } from '../../domain/AddressTor3'; 4 | import { torStringToBuffer } from './torStringToBuffer'; 5 | 6 | /** 7 | * Serializes a Tor v3 address in a Buffer that can be sent 8 | * over the wire. 9 | */ 10 | export function serializeTor3(address: AddressTor3): Buffer { 11 | const writer = new BufferWriter(Buffer.alloc(38)); 12 | 13 | const hostBytes = torStringToBuffer(address.host); 14 | 15 | writer.writeUInt8(address.type); 16 | writer.writeBytes(hostBytes); 17 | writer.writeUInt16BE(address.port); 18 | 19 | return writer.toBuffer(); 20 | } 21 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/address/torStringToBuffer.ts: -------------------------------------------------------------------------------- 1 | import { Base32 } from '@node-dlc/common'; 2 | 3 | /** 4 | * Converts a Tor address in string notation into a Buffer 5 | */ 6 | export function torStringToBuffer(host: string): Buffer { 7 | host = host.substr(0, host.indexOf('.')); 8 | host = host.toUpperCase(); 9 | return Base32.decode(host); 10 | } 11 | -------------------------------------------------------------------------------- /packages/wire/lib/serialize/readTlvs.ts: -------------------------------------------------------------------------------- 1 | import { BufferReader } from '@node-dlc/bufio'; 2 | 3 | /** 4 | * Reads TLVs from a reader until the entire stream is processed. The handler is 5 | * responsible for doing something with the data bytes. 6 | * @param reader 7 | * @param handler 8 | */ 9 | export function readTlvs( 10 | reader: BufferReader, 11 | handler: (type: bigint, value: BufferReader) => boolean, 12 | ) { 13 | let lastType: bigint; 14 | while (!reader.eof) { 15 | const type = reader.readBigSize(); 16 | const len = reader.readBigSize(); 17 | const value = reader.readBytes(Number(len)); 18 | const valueReader = new BufferReader(value); 19 | 20 | if (type <= lastType) { 21 | throw new Error('Invalid TLV stream'); 22 | } 23 | 24 | const isEven = type % BigInt(2) === BigInt(0); 25 | const wasHandled = handler(type, valueReader); 26 | 27 | if (!wasHandled && isEven) { 28 | throw new Error('Unknown even type'); 29 | } 30 | 31 | if (wasHandled && !valueReader.eof) { 32 | throw new Error('Non-canonical length'); 33 | } 34 | 35 | lastType = type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/wire/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@node-dlc/wire", 3 | "version": "0.24.1", 4 | "description": "Lightning Network Wire Protocol", 5 | "scripts": { 6 | "test": "../../node_modules/.bin/nyc --reporter=lcov --reporter=text --extension=.ts ../../node_modules/.bin/mocha --require ts-node/register --recursive \"__tests__/**/*.spec.*\"", 7 | "lint": "../../node_modules/.bin/eslint lib/**/*", 8 | "build": "../../node_modules/.bin/tsc --project ./tsconfig-build.json", 9 | "prepublish": "npm run build" 10 | }, 11 | "keywords": [ 12 | "lightning", 13 | "network", 14 | "bolt2", 15 | "bolt7", 16 | "wire protocol", 17 | "lightning-network" 18 | ], 19 | "author": "Brian Mancini ", 20 | "homepage": "https://github.com/atomicfinance/node-dlc/tree/master/packages/wire", 21 | "license": "MIT", 22 | "main": "dist/index.js", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/atomicfinance/node-dlc.git" 26 | }, 27 | "dependencies": { 28 | "@node-dlc/bitcoin": "^0.24.1", 29 | "@node-dlc/bufio": "^0.24.1", 30 | "@node-dlc/checksum": "^0.24.1", 31 | "@node-dlc/common": "^0.24.1", 32 | "@node-dlc/crypto": "^0.24.1", 33 | "@node-dlc/logger": "^0.24.1", 34 | "@node-dlc/noise": "^0.24.1" 35 | }, 36 | "publishConfig": { 37 | "access": "public" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/wire/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist" 5 | }, 6 | "include": ["lib"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/wire/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["lib", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*.js", 10 | "**/*.jsx" 11 | ], 12 | "exclude": ["node_modules", "dist", "**/dist"] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["DOM", "ES2020"], 5 | "module": "CommonJS", 6 | "declaration": true, 7 | "esModuleInterop": true, 8 | "removeComments": false, 9 | "preserveConstEnums": true, 10 | "sourceMap": true, 11 | "skipLibCheck": true 12 | }, 13 | "exclude": ["node_modules", "**/*.spec.ts", "dist"] 14 | } 15 | --------------------------------------------------------------------------------