├── lib
├── bitcoin.dart
├── src
│ ├── core
│ │ ├── verification_exception.dart
│ │ ├── units.dart
│ │ ├── sig_hash.dart
│ │ ├── params
│ │ │ ├── test_net_params.dart
│ │ │ ├── main_net_params.dart
│ │ │ ├── unit_test_params.dart
│ │ │ └── network_parameters.dart
│ │ ├── transaction_outpoint.dart
│ │ ├── transaction_output.dart
│ │ ├── transaction_input.dart
│ │ ├── filtered_block.dart
│ │ ├── block_header.dart
│ │ ├── address.dart
│ │ ├── transaction_signature.dart
│ │ ├── partial_merkle_tree.dart
│ │ └── bloom_filter.dart
│ ├── wire
│ │ ├── messages
│ │ │ ├── getdata_message.dart
│ │ │ ├── inventory_message.dart
│ │ │ ├── notfound_message.dart
│ │ │ ├── getblocks_message.dart
│ │ │ ├── getheaders_message.dart
│ │ │ ├── verack_message.dart
│ │ │ ├── mempool_message.dart
│ │ │ ├── getaddress_message.dart
│ │ │ ├── filterclear_message.dart
│ │ │ ├── block_message.dart
│ │ │ ├── filterload_message.dart
│ │ │ ├── merkleblock_message.dart
│ │ │ ├── transaction_message.dart
│ │ │ ├── filteradd_message.dart
│ │ │ ├── ping_message.dart
│ │ │ ├── pong_message.dart
│ │ │ ├── headers_message.dart
│ │ │ ├── request_message.dart
│ │ │ ├── address_message.dart
│ │ │ ├── inventory_item_container_message.dart
│ │ │ ├── alert_message.dart
│ │ │ └── version_message.dart
│ │ ├── inventory_item.dart
│ │ ├── peer_address.dart
│ │ ├── serialization.dart
│ │ └── message.dart
│ ├── crypto
│ │ ├── key_crypter_exception.dart
│ │ ├── key_crypter.dart
│ │ └── encrypted_private_key.dart
│ ├── script
│ │ ├── script_exception.dart
│ │ ├── script_builder.dart
│ │ ├── script_chunk.dart
│ │ ├── script_op_codes.dart
│ │ └── script.dart
│ ├── utils
│ │ ├── checksum_buffer.dart
│ │ └── checksum_reader.dart
│ └── crypto.dart
├── scripts
│ ├── common.dart
│ ├── pay_to_pubkey_input.dart
│ ├── pay_to_pubkey_output.dart
│ ├── pay_to_pubkey_hash_input.dart
│ ├── pay_to_pubkey_hash_output.dart
│ ├── multisig_output.dart
│ ├── multisig_input.dart
│ └── pay_to_script_hash_output.dart
├── script.dart
├── wire.dart
├── core.dart
└── crypto
│ ├── key_crypter_scrypt.dart
│ └── mnemonic_code.dart
├── .gitignore
├── test
├── test_config.dart
├── resources
│ ├── sig_canonical.json
│ ├── sig_noncanonical.json
│ ├── tx_valid_notworking.json
│ └── tx_invalid.json
├── wire
│ ├── peer_address_test.dart
│ ├── messages
│ │ ├── alert_message_test.dart
│ │ └── version_message_test.dart
│ ├── bloom_filter_test.dart
│ └── message_serialization_test.dart
├── serialization
│ └── varint_test.dart
├── crypto
│ ├── sha256hash_test.dart
│ └── key_crypter_scrypt_test.dart
└── core
│ ├── address_test.dart
│ └── utils_test.dart
├── README.md
├── pubspec.yaml
└── LICENSE
/lib/bitcoin.dart:
--------------------------------------------------------------------------------
1 | library bitcoin;
2 |
3 | export "core.dart";
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | packages
3 | .project
4 | pubspec.lock
5 | docs/
--------------------------------------------------------------------------------
/test/test_config.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.config;
2 |
3 | final String RESOURCES = "/home/steven/git/dart-bitcoin/test/resources";
4 |
--------------------------------------------------------------------------------
/lib/src/core/verification_exception.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class VerificationException implements Exception {
4 | final String message;
5 | VerificationException([String this.message]);
6 | @override
7 | String toString() => message;
8 | }
9 |
--------------------------------------------------------------------------------
/lib/scripts/common.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.common;
2 |
3 | export "multisig_input.dart";
4 | export "multisig_output.dart";
5 | export "pay_to_pubkey_hash_input.dart";
6 | export "pay_to_pubkey_hash_output.dart";
7 | export "pay_to_pubkey_input.dart";
8 | export "pay_to_pubkey_output.dart";
9 | export "pay_to_script_hash_output.dart";
10 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/getdata_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class GetDataMessage extends InventoryItemContainerMessage {
4 | @override
5 | String get command => Message.CMD_GETDATA;
6 |
7 | GetDataMessage(List items) : super(items);
8 |
9 | /// Create an empty instance.
10 | GetDataMessage.empty();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/inventory_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class InventoryMessage extends InventoryItemContainerMessage {
4 | @override
5 | String get command => Message.CMD_INV;
6 |
7 | InventoryMessage(List items) : super(items);
8 |
9 | /// Create an empty instance.
10 | InventoryMessage.empty();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/notfound_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class NotFoundMessage extends InventoryItemContainerMessage {
4 | @override
5 | String get command => Message.CMD_NOTFOUND;
6 |
7 | NotFoundMessage(List items) : super(items);
8 |
9 | /// Create an empty instance.
10 | NotFoundMessage.empty();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/getblocks_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class GetBlocksMessage extends RequestMessage {
4 | @override
5 | String get command => Message.CMD_GETBLOCKS;
6 |
7 | GetBlocksMessage(List locators, [Hash256 stop]) : super(locators, stop);
8 |
9 | /// Create an empty instance.
10 | GetBlocksMessage.empty() : super.empty();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/getheaders_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class GetHeadersMessage extends RequestMessage {
4 | @override
5 | String get command => Message.CMD_GETHEADERS;
6 |
7 | GetHeadersMessage(List locators, [Hash256 stop]) : super(locators, stop);
8 |
9 | /// Create an empty instance.
10 | GetHeadersMessage.empty() : super.empty();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/core/units.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class Units {
4 | static const int COIN = 100000000;
5 | static const int CENT = 1000000;
6 |
7 | static int toSatoshi(num bitcoins) {
8 | if (bitcoins is int) {
9 | return bitcoins * pow(10, 8);
10 | }
11 | return (bitcoins * pow(10, 8)).truncate();
12 | }
13 |
14 | static num toBitcoins(int satoshi) {
15 | return satoshi / pow(10, 8);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/crypto/key_crypter_exception.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class KeyCrypterException implements Exception {
4 | String message;
5 |
6 | ///Will be either an [Exception] or an [Error].
7 | Object reason;
8 |
9 | KeyCrypterException([String this.message, Object this.reason]);
10 |
11 | @override
12 | String toString() =>
13 | "KeyCrypterException: $message" + (reason == null ? "" : "; Reason: $reason");
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/verack_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class VerackMessage extends Message {
4 | @override
5 | String get command => Message.CMD_VERACK;
6 |
7 | VerackMessage();
8 |
9 | /// Create an empty instance.
10 | VerackMessage.empty();
11 |
12 | @override
13 | void bitcoinDeserialize(bytes.Reader reader, int pver) {}
14 |
15 | @override
16 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {}
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | dart-bitcoin
2 | ============
3 |
4 | Bitcoin library witten in Google Dart (dartlang.org).
5 |
6 | This library is still in very early development so it is far from usable.
7 | Expect a lot refactoring and renaming to happen before it reaches a stable state.
8 |
9 | All help is of course welcome!
10 |
11 | Feel free to ask questions or discuss the development in the mailing list:
12 | https://groups.google.com/forum/#!forum/dartcoin-dev
13 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/mempool_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class MemPoolMessage extends Message {
4 | @override
5 | String get command => Message.CMD_MEMPOOL;
6 |
7 | MemPoolMessage();
8 |
9 | /// Create an empty instance.
10 | MemPoolMessage.empty();
11 |
12 | @override
13 | void bitcoinDeserialize(bytes.Reader reader, int pver) {}
14 |
15 | @override
16 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {}
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/getaddress_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class GetAddressMessage extends Message {
4 | @override
5 | String get command => Message.CMD_GETADDR;
6 |
7 | GetAddressMessage();
8 |
9 | /// Create an empty instance.
10 | GetAddressMessage.empty();
11 |
12 | @override
13 | void bitcoinDeserialize(bytes.Reader reader, int pver) {}
14 |
15 | @override
16 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {}
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/crypto/key_crypter.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | abstract class KeyCrypter {
4 | /**
5 | * Derive the decryption key from a passphrase.
6 | *
7 | * Note that passphrase should be encodable as UTF-8.
8 | */
9 | KeyParameter deriveKey(String passphrase);
10 |
11 | EncryptedPrivateKey encrypt(Uint8List privKey, KeyParameter encryptionKey);
12 |
13 | Uint8List decrypt(EncryptedPrivateKey encryptedPrivKey, KeyParameter decryptionKey);
14 | }
15 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/filterclear_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class FilterClearMessage extends Message {
4 | @override
5 | String get command => Message.CMD_FILTERCLEAR;
6 |
7 | FilterClearMessage();
8 |
9 | /// Create an empty instance.
10 | FilterClearMessage.empty();
11 |
12 | @override
13 | void bitcoinDeserialize(bytes.Reader reader, int pver) {}
14 |
15 | @override
16 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {}
17 | }
18 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: bitcoin
2 | version: 0.0.0
3 | author: Steven Roose
4 | description: A Bitcoin library for Dart. Pub-test release.
5 | homepage: http://www.dartcoin.org/
6 | dependencies:
7 | asn1lib: ">=0.3.1"
8 | base58check: ^1.0.0
9 | bignum: ">=0.0.4"
10 | bytes: ">=0.2.0 <0.3.0"
11 | collection: any
12 | cryptoutils: ">=0.1.8"
13 | pointycastle: any
14 | dev_dependencies:
15 | browser: any
16 | test: any
17 | uuid: any
18 |
--------------------------------------------------------------------------------
/test/resources/sig_canonical.json:
--------------------------------------------------------------------------------
1 | [
2 | "300602010002010001",
3 | "3008020200ff020200ff01",
4 | "304402203932c892e2e550f3af8ee4ce9c215a87f9bb831dcac87b2838e2c2eaa891df0c022030b61dd36543125d56b9f9f3a1f9353189e5af33cdda8d77a5209aec03978fa001",
5 | "30450220076045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688c2eca1d2ba7f6b180620eaa03488e6585db6ba01",
6 | "3046022100876045be6f9eca28ff1ec606b833d0b87e70b2a630f5e3a496b110967a40f90a0221008fffd599910eefe00bc803c688c2eca1d2ba7f6b180620eaa03488e6585db6ba01"
7 | ]
8 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/block_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class BlockMessage extends Message {
4 | @override
5 | String get command => Message.CMD_BLOCK;
6 |
7 | Block block;
8 |
9 | BlockMessage(Block this.block);
10 |
11 | /// Create an empty instance.
12 | BlockMessage.empty();
13 |
14 | @override
15 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
16 | block = readObject(reader, new Block.empty(), pver) as Block;
17 | }
18 |
19 | @override
20 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
21 | writeObject(buffer, block, pver);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/script/script_exception.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.script;
2 |
3 | class ScriptException extends VerificationException {
4 | //TODO
5 |
6 | final Script script;
7 | final int opcode;
8 |
9 | ScriptException([String message, Script this.script, int this.opcode])
10 | : super(message);
11 |
12 | @override
13 | String toString() => "ScriptException: $message";
14 |
15 | @override
16 | bool operator ==(dynamic other) {
17 | if (other.runtimeType == ScriptException) return false;
18 | return message == other.message &&
19 | script == other.script &&
20 | opcode == other.opcode;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/filterload_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class FilterLoadMessage extends Message {
4 | @override
5 | String get command => Message.CMD_FILTERLOAD;
6 |
7 | BloomFilter filter;
8 |
9 | FilterLoadMessage(BloomFilter this.filter);
10 |
11 | /// Create an empty instance.
12 | FilterLoadMessage.empty();
13 |
14 | @override
15 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
16 | filter = readObject(reader, new BloomFilter.empty(), pver);
17 | }
18 |
19 | @override
20 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
21 | writeObject(buffer, filter, pver);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/merkleblock_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class MerkleBlockMessage extends Message {
4 | @override
5 | String get command => Message.CMD_MERKLEBLOCK;
6 |
7 | FilteredBlock block;
8 |
9 | MerkleBlockMessage(FilteredBlock this.block);
10 |
11 | /// Create an empty instance.
12 | MerkleBlockMessage.empty();
13 |
14 | @override
15 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
16 | block = readObject(reader, new FilteredBlock.empty(), pver);
17 | }
18 |
19 | @override
20 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
21 | writeObject(buffer, block, pver);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/transaction_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class TransactionMessage extends Message {
4 | @override
5 | String get command => Message.CMD_TX;
6 |
7 | Transaction transaction;
8 |
9 | TransactionMessage(Transaction this.transaction) {}
10 |
11 | /// Create an empty instance.
12 | TransactionMessage.empty();
13 |
14 | @override
15 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
16 | transaction = readObject(reader, new Transaction.empty(), pver);
17 | }
18 |
19 | @override
20 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
21 | writeObject(buffer, transaction, pver);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/script.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.script;
2 |
3 | import "dart:collection";
4 | import "dart:typed_data";
5 |
6 | import "package:bignum/bignum.dart";
7 | import "package:bytes/bytes.dart" as bytes;
8 | import "package:cryptoutils/cryptoutils.dart";
9 |
10 | import "package:bitcoin/core.dart";
11 | import "package:bitcoin/scripts/common.dart";
12 | import "package:bitcoin/src/crypto.dart" as crypto;
13 | import "package:bitcoin/src/utils.dart" as utils;
14 |
15 | part "src/script/script.dart";
16 | part "src/script/script_builder.dart";
17 | part "src/script/script_chunk.dart";
18 | part "src/script/script_exception.dart";
19 | part "src/script/script_executor.dart";
20 | part "src/script/script_op_codes.dart";
21 |
--------------------------------------------------------------------------------
/lib/src/core/sig_hash.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class SigHash {
4 | static const SigHash ALL = const SigHash._(1);
5 | static const SigHash NONE = const SigHash._(2);
6 | static const SigHash SINGLE = const SigHash._(3);
7 |
8 | static const int ANYONE_CAN_PAY = 0x80;
9 |
10 | /**
11 | * The bit-value of this SigHash flag.
12 | *
13 | * Note that in BitcoinJ, this value is retrieved by doing sigHash.ordinal() + 1.
14 | */
15 | final int value;
16 |
17 | const SigHash._(int this.value);
18 |
19 | static int sigHashFlagsValue(SigHash sh, bool anyoneCanPay) {
20 | int val = sh.value;
21 | if (anyoneCanPay) val |= ANYONE_CAN_PAY;
22 | return val;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/filteradd_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class FilterAddMessage extends Message {
4 | @override
5 | String get command => Message.CMD_FILTERADD;
6 |
7 | static const int MAX_DATA_SIZE = 520;
8 |
9 | Uint8List data;
10 |
11 | FilterAddMessage(Uint8List this.data) {
12 | if (data.length > MAX_DATA_SIZE) throw new ArgumentError("Data attribute is too large.");
13 | }
14 |
15 | /// Create an empty instance.
16 | FilterAddMessage.empty();
17 |
18 | @override
19 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
20 | data = readByteArray(reader);
21 | }
22 |
23 | @override
24 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
25 | writeByteArray(buffer, data);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/src/core/params/test_net_params.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class _TestNetParams extends NetworkParameters {
4 | static Block _genesis;
5 |
6 | const _TestNetParams()
7 | : super._(
8 | addressHeader: 111,
9 | p2shHeader: 196,
10 | magicValue: 0x0709110B,
11 | id: "org.bitcoin.test",
12 | port: 18333);
13 |
14 | Block get genesisBlock {
15 | if (_genesis == null) {
16 | Block genesis = NetworkParameters._createGenesis(this)
17 | ..timestamp = 1296688602
18 | ..nonce = 414098458
19 | ..difficultyTarget = 0x1d00ffff;
20 | _genesis = genesis;
21 | }
22 | return _genesis;
23 | }
24 |
25 | BigInteger get proofOfWorkLimit => utils.decodeCompactBits(0x1d00ffff);
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/core/params/main_net_params.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class _MainNetParams extends NetworkParameters {
4 | static Block _genesis;
5 |
6 | const _MainNetParams()
7 | : super._(
8 | addressHeader: 0,
9 | p2shHeader: 5,
10 | magicValue: 0xD9B4BEF9,
11 | id: "org.bitcoin.production",
12 | port: 8333);
13 |
14 | Block get genesisBlock {
15 | if (_genesis == null) {
16 | Block genesis = NetworkParameters._createGenesis(this)
17 | ..timestamp = 1231006505
18 | ..nonce = 2083236893
19 | ..difficultyTarget = 0x1d00ffff;
20 | _genesis = genesis;
21 | }
22 | return _genesis;
23 | }
24 |
25 | BigInteger get proofOfWorkLimit => utils.decodeCompactBits(0x1d00ffff);
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/utils/checksum_buffer.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.src.utils.checksum_buffer;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bytes/src/buffer_impl.dart";
6 | import "package:pointycastle/api.dart";
7 |
8 | /// A [Buffer] implementation that checksums all data put into it.
9 | class ChecksumBuffer extends BufferImpl {
10 | final Digest digest;
11 |
12 | ChecksumBuffer(Digest this.digest) : super(false);
13 |
14 | Uint8List checksum() {
15 | Uint8List sum = new Uint8List(digest.digestSize);
16 | digest.doFinal(sum, 0);
17 | return sum;
18 | }
19 |
20 | @override
21 | void add(List bytes) {
22 | super.add(bytes);
23 | digest.update(bytes, 0, bytes.length);
24 | }
25 |
26 | @override
27 | void clear() {
28 | super.clear();
29 | digest.reset();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/ping_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class PingMessage extends Message {
4 | @override
5 | String get command => Message.CMD_PING;
6 |
7 | int nonce;
8 |
9 | PingMessage([
10 | int this.nonce = null,
11 | ]) {
12 | if (nonce != null && nonce < 0) throw new Exception("Nonce value should be at least zero");
13 | }
14 |
15 | factory PingMessage.generate() {
16 | int nonce = new Random().nextInt(1 << 8);
17 | return new PingMessage(nonce);
18 | }
19 |
20 | /// Create an empty instance.
21 | PingMessage.empty();
22 |
23 | bool get hasNonce => nonce != null;
24 |
25 | @override
26 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
27 | nonce = readUintLE(reader, 8);
28 | }
29 |
30 | @override
31 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
32 | if (hasNonce)
33 | writeUintLE(buffer, nonce, 8);
34 | else
35 | writeUintLE(buffer, 0, 8);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/core/params/unit_test_params.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class _UnitTestParams extends NetworkParameters {
4 | static Block _genesis;
5 |
6 | const _UnitTestParams()
7 | : super._(
8 | addressHeader: 111,
9 | p2shHeader: 196,
10 | magicValue: 0x0b110907,
11 | // copied from bitcoinj to indicate that we use the same params as bitcoinj
12 | id: "com.google.bitcoin.unittest",
13 | port: 18333);
14 |
15 | Block get genesisBlock {
16 | if (_genesis == null) {
17 | Block genesis = NetworkParameters._createGenesis(this)
18 | ..timestamp = new DateTime.now().millisecondsSinceEpoch ~/ 1000
19 | ..difficultyTarget = Block.EASIEST_DIFFICULTY_TARGET
20 | ..solve(this);
21 | _genesis = genesis;
22 | }
23 | return _genesis;
24 | }
25 |
26 | BigInteger get proofOfWorkLimit =>
27 | new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
28 | }
29 |
--------------------------------------------------------------------------------
/lib/src/script/script_builder.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.script;
2 |
3 | class ScriptBuilder {
4 | List _chunks;
5 |
6 | /**
7 | * Initialize a new [ScriptBuilder].
8 | */
9 | ScriptBuilder() {
10 | _chunks = new List();
11 | }
12 |
13 | ScriptBuilder op(int opcode) {
14 | _chunks.add(new ScriptChunk.opCodeChunk(opcode));
15 | return this;
16 | }
17 |
18 | ScriptBuilder data(Uint8List data) {
19 | _chunks.add(new ScriptChunk.dataChunk(data));
20 | return this;
21 | }
22 |
23 | ScriptBuilder smallNum(int num) {
24 | _chunks.add(new ScriptChunk.opCodeChunk(Script.encodeToOpN(num)));
25 | return this;
26 | }
27 |
28 | Script build([bool encoded = false]) {
29 | return new Script(encoded ? buildBytes() : new List.from(_chunks));
30 | }
31 |
32 | Uint8List buildBytes() {
33 | var buffer = new bytes.Buffer();
34 | for (ScriptChunk chunk in _chunks) {
35 | buffer.add(chunk.data);
36 | }
37 | return buffer.asBytes();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/wire/peer_address_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.wire.peer_address;
2 |
3 | import "package:test/test.dart";
4 | import "package:cryptoutils/cryptoutils.dart";
5 |
6 | import "package:bitcoin/wire.dart";
7 |
8 | void main() {
9 | group("wire.PeerAddress", () {
10 | test("peerAddressRoundtrip", () {
11 | // copied verbatim from https://en.bitcoin.it/wiki/Protocol_specification#Network_address
12 | String fromSpec = "010000000000000000000000000000000000ffff0a000001208d";
13 | PeerAddress pa =
14 | new PeerAddress.fromBitcoinSerialization(CryptoUtils.hexToBytes(fromSpec), 0);
15 | String reserialized = CryptoUtils.bytesToHex(pa.bitcoinSerializedBytes(0));
16 | expect(reserialized, equals(fromSpec));
17 | });
18 |
19 | test("serialize", () {
20 | PeerAddress pa = new PeerAddress("127.0.0.1", port: 8333);
21 | expect(CryptoUtils.bytesToHex(pa.bitcoinSerializedBytes(0)),
22 | equals("000000000000000000000000000000000000ffff7f000001208d"));
23 | });
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/pong_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class PongMessage extends Message {
4 | @override
5 | String get command => Message.CMD_PONG;
6 |
7 | /** The smallest protocol version that supports the pong response (BIP 31). Anything beyond version 60000. */
8 | static const int MIN_PROTOCOL_VERSION = 60001;
9 |
10 | int nonce;
11 |
12 | PongMessage([
13 | int this.nonce = null,
14 | ]) {
15 | if (nonce != null && nonce < 0) throw new Exception("Nonce value should be at least zero");
16 | }
17 |
18 | PongMessage.fromPing(PingMessage ping) {
19 | nonce = ping.nonce;
20 | }
21 |
22 | /// Create an empty instance.
23 | PongMessage.empty();
24 |
25 | bool get hasNonce => nonce != null;
26 |
27 | @override
28 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
29 | nonce = readUintLE(reader, 8);
30 | }
31 |
32 | @override
33 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
34 | if (hasNonce)
35 | writeUintLE(buffer, nonce, 8);
36 | else
37 | writeUintLE(buffer, 0, 8);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 dartcoin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/headers_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class HeadersMessage extends Message {
4 | @override
5 | String get command => Message.CMD_HEADERS;
6 |
7 | List headers;
8 |
9 | HeadersMessage(List this.headers);
10 |
11 | /// Create an empty instance.
12 | HeadersMessage.empty();
13 |
14 | void addHeader(BlockHeader header) {
15 | headers.add(header);
16 | }
17 |
18 | void removeHeader(BlockHeader header) {
19 | headers.remove(header);
20 | }
21 |
22 | @override
23 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
24 | int nbHeaders = readVarInt(reader);
25 | List newHeaders = new List(nbHeaders);
26 | for (int i = 0; i < nbHeaders; i++) {
27 | newHeaders[i] = readObject(reader, new BlockHeader.empty(), pver);
28 | }
29 | headers = newHeaders;
30 | }
31 |
32 | @override
33 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
34 | writeVarInt(buffer, headers.length);
35 | for (BlockHeader header in headers) {
36 | header.bitcoinSerializeAsEmptyBlock(buffer, pver);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/utils/checksum_reader.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.src.utils.checksum_reader;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bytes/bytes.dart";
6 | import "package:bytes/src/reader_base.dart";
7 | import "package:pointycastle/api.dart";
8 |
9 | /// A [Reader] implementation that checksums all data read from it.
10 | class ChecksumReader extends ReaderBase {
11 | final Reader reader;
12 | final Digest digest;
13 |
14 | ChecksumReader(Reader this.reader, Digest this.digest);
15 |
16 | Uint8List checksum() {
17 | Uint8List sum = new Uint8List(digest.digestSize);
18 | digest.doFinal(sum, 0);
19 | return sum;
20 | }
21 |
22 | @override
23 | int get remainingLength => reader.remainingLength;
24 |
25 | @override
26 | int get length => reader.length;
27 |
28 | @override
29 | List readBytes(int n) {
30 | Uint8List bytes = reader.readBytes(n);
31 | digest.update(bytes, 0, bytes.length);
32 | return bytes;
33 | }
34 |
35 | @override
36 | int readBytesInto(List b) {
37 | int n = reader.readBytesInto(b);
38 | Uint8List copy = new Uint8List.fromList(b);
39 | digest.update(copy, 0, n);
40 | return n;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/request_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | abstract class RequestMessage extends Message {
4 | List locators;
5 | Hash256 stop;
6 | int protocolVersion;
7 |
8 | RequestMessage(List this.locators, [Hash256 this.stop]) {
9 | stop = stop ?? Hash256.ZERO_HASH;
10 | }
11 |
12 | void addLocator(Hash256 locator) {
13 | locators.add(locator);
14 | }
15 |
16 | void removeLocator(Hash256 locator) {
17 | locators.remove(locator);
18 | }
19 |
20 | /// Create an empty instance.
21 | RequestMessage.empty();
22 |
23 | @override
24 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
25 | protocolVersion = readUintLE(reader);
26 | int nbLocators = readVarInt(reader);
27 | locators = new List(nbLocators);
28 | for (int i = 0; i < nbLocators; i++) {
29 | locators.add(readSHA256(reader));
30 | }
31 | stop = readSHA256(reader);
32 | }
33 |
34 | @override
35 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
36 | writeUintLE(buffer, protocolVersion);
37 | writeVarInt(buffer, locators.length);
38 | for (Hash256 hash in locators) {
39 | writeSHA256(buffer, hash);
40 | }
41 | writeSHA256(buffer, stop);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/src/script/script_chunk.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.script;
2 |
3 | class ScriptChunk {
4 | /// Only set if opCode, otherwise null
5 | int opCode;
6 |
7 | /// Only set if not opcode, otherwise null
8 | Uint8List data;
9 |
10 | int startLocationInProgram;
11 |
12 | ScriptChunk.opCodeChunk(int this.opCode, [int this.startLocationInProgram]) {
13 | opCode = opCode & 0xff;
14 | }
15 |
16 | ScriptChunk.dataChunk(Uint8List this.data, [int this.startLocationInProgram]);
17 |
18 | bool get isOpCode => opCode != null;
19 |
20 | @override
21 | String toString() {
22 | if (isOpCode)
23 | return ScriptOpCodes.getOpCodeName(opCode);
24 | else
25 | return "[" + CryptoUtils.bytesToHex(data) + "]";
26 | }
27 |
28 | Uint8List serialize() {
29 | if (isOpCode) {
30 | return new Uint8List.fromList([opCode]);
31 | } else {
32 | return Script.encodeData(data);
33 | }
34 | }
35 |
36 | @override
37 | bool operator ==(dynamic other) {
38 | if (other.runtimeType != ScriptChunk) return false;
39 | return isOpCode == other.isOpCode && utils.equalLists(serialize(), other.serialize());
40 | }
41 |
42 | @override
43 | int get hashCode => (isOpCode ? 0xffff : 0) ^ utils.listHashCode(serialize());
44 | }
45 |
--------------------------------------------------------------------------------
/test/serialization/varint_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.serialization.varint;
2 |
3 | import "package:bytes/bytes.dart";
4 |
5 | import "package:test/test.dart";
6 |
7 | import "package:bitcoin/src/wire/serialization.dart";
8 |
9 | ReaderBuffer buffer;
10 |
11 | void main() {
12 | group("serialization.VarInt", () {
13 | setUp(() {
14 | buffer = new ReaderBuffer();
15 | });
16 |
17 | test("varint_byte", () {
18 | writeVarInt(buffer, 10);
19 | expect(buffer.length, equals(1));
20 | expect(buffer.asBytes().length, equals(1));
21 | expect(readVarInt(buffer), equals(10));
22 | });
23 |
24 | test("varint_short", () {
25 | writeVarInt(buffer, 64000);
26 | expect(buffer.length, equals(3));
27 | expect(buffer.asBytes().length, equals(3));
28 | expect(readVarInt(buffer), equals(64000));
29 | });
30 |
31 | test("varint_int", () {
32 | writeVarInt(buffer, 0xAABBCCDD);
33 | expect(buffer.length, equals(5));
34 | expect(buffer.asBytes().length, equals(5));
35 | expect(readVarInt(buffer), equals(0xAABBCCDD));
36 | });
37 |
38 | test("varint_long", () {
39 | writeVarInt(buffer, 0xCAFEBABEDEADBEEF);
40 | expect(buffer.length, equals(9));
41 | expect(buffer.asBytes().length, equals(9));
42 | expect(readVarInt(buffer), equals(0xCAFEBABEDEADBEEF));
43 | });
44 | });
45 | }
46 |
--------------------------------------------------------------------------------
/lib/src/crypto/encrypted_private_key.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class EncryptedPrivateKey {
4 | // the actual key
5 | Uint8List _encryptedKey;
6 | // the initialisation vector
7 | Uint8List _iv;
8 |
9 | EncryptedPrivateKey(Uint8List encryptedKey, Uint8List iv) {
10 | _encryptedKey = encryptedKey;
11 | _iv = iv;
12 | }
13 |
14 | Uint8List get encryptedKey {
15 | if (_encryptedKey == null) return null;
16 | return new Uint8List.fromList(_encryptedKey);
17 | }
18 |
19 | Uint8List get iv {
20 | if (_iv == null) return null;
21 | return new Uint8List.fromList(_iv);
22 | }
23 |
24 | /**
25 | * Clone this encrypted private key.
26 | */
27 | EncryptedPrivateKey clone() => new EncryptedPrivateKey(this._encryptedKey, this._iv);
28 |
29 | @override
30 | operator ==(dynamic other) {
31 | if (other.runtimeType != EncryptedPrivateKey) return false;
32 | if (identical(this, other)) return true;
33 | return _iv == other._iv && _encryptedKey == other._encryptedKey;
34 | }
35 |
36 | @override
37 | int get hashCode => utils.listHashCode(_encryptedKey) ^ utils.listHashCode(_iv);
38 |
39 | @override
40 | String toString() =>
41 | "EncryptedPrivateKey [initialisationVector=$_iv, encryptedPrivateBytes=$_encryptedKey]";
42 |
43 | /**
44 | * Clear this private key.
45 | */
46 | void clear() {
47 | _iv = null;
48 | _encryptedKey = null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/address_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class AddressMessage extends Message {
4 | @override
5 | String get command => Message.CMD_ADDR;
6 |
7 | static const int MAX_ADDRESSES = 1024;
8 |
9 | List addresses;
10 |
11 | /**
12 | * Create a new address message with the given list of addresses.
13 | */
14 | AddressMessage([List this.addresses]) {
15 | addresses = addresses ?? new List();
16 | }
17 |
18 | /// Create an empty instance.
19 | AddressMessage.empty();
20 |
21 | void addAddress(PeerAddress address) {
22 | //TODO look at peeraddress btcd
23 | addresses.add(address);
24 | }
25 |
26 | void removeAddress(PeerAddress address) {
27 | addresses.remove(address);
28 | }
29 |
30 | @override
31 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
32 | int nbAddrs = readVarInt(reader);
33 | if (nbAddrs > MAX_ADDRESSES)
34 | throw new SerializationException("Too many addresses in AddressMessage");
35 | List newAddresses = new List(nbAddrs);
36 | for (int i = 0; i < nbAddrs; i++) {
37 | newAddresses[i] = readObject(reader, new PeerAddress.empty(), pver);
38 | }
39 | addresses = newAddresses;
40 | }
41 |
42 | @override
43 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
44 | writeVarInt(buffer, addresses.length);
45 | addresses.forEach((a) => writeObject(buffer, a, pver));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/scripts/pay_to_pubkey_input.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.input.pay_to_pubkey;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bitcoin/core.dart";
6 | import "package:bitcoin/script.dart";
7 |
8 | class PayToPubKeyInputScript extends Script {
9 | /**
10 | *
11 | *
12 | * The value for [signature] can be either a [TransactionSignature] or a [Uint8List].
13 | *
14 | * If [encoded] is set to false, the script will be built using chunks. This improves
15 | * performance when the script is intended for execution.
16 | */
17 | factory PayToPubKeyInputScript(dynamic signature) {
18 | if (signature is TransactionSignature) signature = signature.encodeToDER();
19 | if (!(signature is Uint8List))
20 | throw new ArgumentError(
21 | "The value for signature can be either a TransactionSignature or a Uint8List.");
22 | return new PayToPubKeyInputScript.convert(new ScriptBuilder().data(signature).build(), true);
23 | }
24 |
25 | PayToPubKeyInputScript.convert(Script script, [bool skipCheck = false]) : super(script.program) {
26 | if (!skipCheck && !matchesType(script))
27 | throw new ScriptException("Given script is not an instance of this script type.");
28 | }
29 |
30 | TransactionSignature get signature => new TransactionSignature.deserialize(chunks[0].data);
31 |
32 | /**
33 | * Script must contain only one chunk, the signature data chunk.
34 | */
35 | static bool matchesType(Script script) {
36 | return script.chunks.length == 1 && script.chunks[0].data.length > 1;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/scripts/pay_to_pubkey_output.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.output.pay_to_pubkey;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bitcoin/core.dart";
6 | import "package:bitcoin/script.dart";
7 |
8 | class PayToPubKeyOutputScript extends Script {
9 | /**
10 | * Create a new output for a given public key.
11 | *
12 | * The public key can be either of type Uint8List or KeyPair.
13 | *
14 | * If [encoded] is set to false, the script will be built using chunks. This improves
15 | * performance when the script is intended for execution.
16 | */
17 | factory PayToPubKeyOutputScript(dynamic pubKey) {
18 | if (pubKey is KeyPair) pubKey = pubKey.publicKey;
19 | if (!(pubKey is Uint8List))
20 | throw new ArgumentError("The public key can be either of type Uint8List or KeyPair.");
21 | return new PayToPubKeyOutputScript.convert(
22 | new ScriptBuilder().data(pubKey).op(ScriptOpCodes.OP_CHECKSIG).build(), true);
23 | }
24 |
25 | PayToPubKeyOutputScript.convert(Script script, [bool skipCheck = false]) : super(script.program) {
26 | if (!skipCheck && !matchesType(script))
27 | throw new ScriptException("Given script is not an instance of this script type.");
28 | }
29 |
30 | KeyPair get pubKey => new KeyPair.public(chunks[0].data);
31 |
32 | Address getAddress([NetworkParameters params]) => pubKey.getAddress(params);
33 |
34 | static bool matchesType(Script script) {
35 | return script.chunks.length == 2 &&
36 | script.chunks[0].data.length > 1 &&
37 | script.chunks[1].opCode == ScriptOpCodes.OP_CHECKSIG;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/test/wire/messages/alert_message_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.wire.alert_message;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bytes/bytes.dart";
6 | import "package:cryptoutils/cryptoutils.dart";
7 |
8 | import "package:test/test.dart";
9 |
10 | import "package:bitcoin/core.dart";
11 | import "package:bitcoin/wire.dart";
12 |
13 | Uint8List _TEST_KEY_PRIV =
14 | CryptoUtils.hexToBytes("6421e091445ade4b24658e96aa60959ce800d8ea9e7bd8613335aa65ba8d840b");
15 |
16 | void main() {
17 | group("wire.messages.AlertMessage", () {
18 | test("deserialize", () {
19 | // A CAlert taken from the reference implementation.
20 | Uint8List payload = CryptoUtils.hexToBytes(
21 | "5c010000004544eb4e000000004192ec4e00000000eb030000e9030000000000000048ee00000088130000002f43416c6572742073797374656d20746573743a2020202020202020207665722e302e352e3120617661696c61626c6500473045022100ec799908c008b272d5e5cd5a824abaaac53d210cc1fa517d8e22a701ecdb9e7002206fa1e7e7c251d5ba0d7c1fe428fc1870662f2927531d1cad8d4581b45bc4f8a7");
22 |
23 | AlertMessage alert = new AlertMessage.empty();
24 | alert.bitcoinDeserialize(new Reader(payload), 0);
25 |
26 | expect(alert.relayUntil.millisecondsSinceEpoch ~/ 1000, equals(1324041285));
27 | expect(alert.expiration.millisecondsSinceEpoch ~/ 1000, equals(1324126785));
28 | expect(alert.id, equals(1003));
29 | expect(alert.cancel, equals(1001));
30 | expect(alert.minVer, equals(0));
31 | expect(alert.maxVer, equals(61000));
32 | expect(alert.priority, equals(5000));
33 | expect(alert.statusBar, equals("CAlert system test: ver.0.5.1 available"));
34 | expect(alert.isSignatureValid(new KeyPair.private(_TEST_KEY_PRIV).publicKey), isTrue);
35 | });
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/wire/inventory_item.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class InventoryItemType {
4 | static const ERROR = const InventoryItemType._(0);
5 | static const MSG_TX = const InventoryItemType._(1);
6 | static const MSG_BLOCK = const InventoryItemType._(2);
7 |
8 | static get values => [ERROR, MSG_TX, MSG_BLOCK];
9 |
10 | final int value;
11 |
12 | const InventoryItemType._(int this.value);
13 | }
14 |
15 | class InventoryItem extends BitcoinSerializable {
16 | static const int SERIALIZATION_LENGTH = 4 + Hash256.LENGTH;
17 |
18 | InventoryItemType type;
19 | Hash256 hash;
20 |
21 | InventoryItem(InventoryItemType this.type, Hash256 this.hash) {
22 | if (type == null || hash == null)
23 | throw new ArgumentError("None of the attributes should be null");
24 | }
25 |
26 | InventoryItem.fromTransaction(Transaction tx) : this(InventoryItemType.MSG_TX, tx.hash);
27 | InventoryItem.fromBlock(BlockHeader block) : this(InventoryItemType.MSG_BLOCK, block.hash);
28 |
29 | // required for serialization
30 | InventoryItem.empty();
31 |
32 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
33 | type = new InventoryItemType._(readUintLE(reader));
34 | hash = readSHA256(reader);
35 | }
36 |
37 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
38 | writeUintLE(buffer, type.value);
39 | writeSHA256(buffer, hash);
40 | }
41 |
42 | @override
43 | int get hashCode {
44 | return type.hashCode + hash.hashCode;
45 | // because hash collision is negligible, _hash.hashCode is enough. but we do it this way for elegance
46 | }
47 |
48 | @override
49 | bool operator ==(dynamic other) {
50 | if (other.runtimeType != InventoryItem) return false;
51 | return type == other.type && hash == other.hash;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/core/transaction_outpoint.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class TransactionOutPoint extends BitcoinSerializable {
4 | static const int SERIALIZATION_LENGTH = 36;
5 |
6 | Hash256 txid;
7 | int index;
8 |
9 | /// Can be `null` when this object has been created by deserialization.
10 | Transaction transaction;
11 |
12 | TransactionOutPoint({Transaction this.transaction, int this.index: 0, Hash256 this.txid}) {
13 | if (transaction != null) txid = transaction.hash;
14 | if (index == -1) index = 0xFFFFFFFF;
15 | txid = txid ?? Hash256.ZERO_HASH;
16 | }
17 |
18 | factory TransactionOutPoint.fromBitcoinSerialization(Uint8List serialization, int pver) {
19 | var reader = new bytes.Reader(serialization);
20 | var obj = new TransactionOutPoint.empty();
21 | obj.bitcoinDeserialize(reader, pver);
22 | return obj;
23 | }
24 |
25 | /// Create an empty instance.
26 | TransactionOutPoint.empty();
27 |
28 | TransactionOutput get connectedOutput {
29 | if (transaction == null) return null;
30 | return transaction.outputs[index];
31 | }
32 |
33 | @override
34 | operator ==(dynamic other) {
35 | if (other.runtimeType != TransactionOutPoint) return false;
36 | if (identical(this, other)) return true;
37 | return txid == other.txid &&
38 | index == other.index &&
39 | (transaction == null || other.transaction == null || transaction == other.transaction);
40 | }
41 |
42 | @override
43 | int get hashCode {
44 | return index.hashCode ^ txid.hashCode;
45 | }
46 |
47 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
48 | writeSHA256(buffer, txid);
49 | writeUintLE(buffer, index);
50 | }
51 |
52 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
53 | txid = readSHA256(reader);
54 | index = readUintLE(reader);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/wire.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.wire;
2 |
3 | import "dart:collection";
4 | import "dart:convert";
5 | import "dart:math";
6 | import "dart:typed_data";
7 |
8 | import "package:bignum/bignum.dart";
9 | import "package:bytes/bytes.dart" as bytes;
10 | import "package:cryptoutils/cryptoutils.dart";
11 |
12 | import "core.dart";
13 | import "src/utils/checksum_buffer.dart";
14 | import "src/utils/checksum_reader.dart";
15 | import "src/wire/serialization.dart";
16 | import "src/crypto.dart" as crypto;
17 | import "src/utils.dart" as utils;
18 |
19 | export "src/wire/serialization.dart" show BitcoinSerializable, SerializationException;
20 |
21 | // wire
22 | part "src/wire/inventory_item.dart";
23 | part "src/wire/message.dart";
24 | part "src/wire/peer_address.dart";
25 | // messages
26 | part "src/wire/messages/version_message.dart";
27 | part "src/wire/messages/verack_message.dart";
28 | part "src/wire/messages/address_message.dart";
29 | part "src/wire/messages/inventory_item_container_message.dart";
30 | part "src/wire/messages/inventory_message.dart";
31 | part "src/wire/messages/getdata_message.dart";
32 | part "src/wire/messages/notfound_message.dart";
33 | part "src/wire/messages/request_message.dart";
34 | part "src/wire/messages/getblocks_message.dart";
35 | part "src/wire/messages/getheaders_message.dart";
36 | part "src/wire/messages/transaction_message.dart";
37 | part "src/wire/messages/block_message.dart";
38 | part "src/wire/messages/headers_message.dart";
39 | part "src/wire/messages/getaddress_message.dart";
40 | part "src/wire/messages/mempool_message.dart";
41 | part "src/wire/messages/ping_message.dart";
42 | part "src/wire/messages/pong_message.dart";
43 | part "src/wire/messages/alert_message.dart";
44 | part "src/wire/messages/filterload_message.dart";
45 | part "src/wire/messages/filteradd_message.dart";
46 | part "src/wire/messages/filterclear_message.dart";
47 | part "src/wire/messages/merkleblock_message.dart";
48 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/inventory_item_container_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | abstract class InventoryItemContainerMessage extends Message {
4 | List kaka;
5 |
6 | InventoryItemContainerMessage([List this.kaka]) {
7 | if (kaka != null && kaka.length > 50000) {
8 | throw new Exception("Maximum 50000 inventory items");
9 | }
10 | }
11 |
12 | /**
13 | * Add a new item to this container message.
14 | *
15 | * [item] can be either of type [InventoryItem], [Block] or [Transaction].
16 | */
17 | void addItem(dynamic item) {
18 | item = _castItem(item);
19 | kaka.add(item);
20 | }
21 |
22 | /**
23 | * Remove an item from this container message.
24 | *
25 | * [item] can be either of type [InventoryItem], [Block] or [Transaction].
26 | */
27 | void removeItem(dynamic item) {
28 | item = _castItem(item);
29 | kaka.remove(item);
30 | }
31 |
32 | InventoryItem _castItem(dynamic item) {
33 | if (item is Block)
34 | item = new InventoryItem.fromBlock(item);
35 | else if (item is Transaction)
36 | item = new InventoryItem.fromTransaction(item);
37 | else if (item is InventoryItem) return item;
38 | throw new ArgumentError("Invalid parameter type. Read documentation.");
39 | }
40 |
41 | /// Create an empty instance.
42 | InventoryItemContainerMessage.empty();
43 |
44 | @override
45 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
46 | int nbItems = readVarInt(reader);
47 | kaka = new List();
48 | for (int i = 0; i < nbItems; i++) {
49 | kaka.add(readObject(reader, new InventoryItem.empty(), pver));
50 | }
51 | }
52 |
53 | @override
54 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
55 | writeVarInt(buffer, kaka.length);
56 | for (InventoryItem item in kaka) {
57 | writeObject(buffer, item, pver);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/scripts/pay_to_pubkey_hash_input.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.input.pay_to_pubkey_hash;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bitcoin/core.dart";
6 | import "package:bitcoin/script.dart";
7 |
8 | class PayToPubKeyHashInputScript extends Script {
9 | /**
10 | *
11 | *
12 | * The value for [signature] can be either a [TransactionSignature] or a [Uint8List].
13 | * [pubKey] can be either of type [KeyPair] or [Uint8List].
14 | *
15 | * If [encoded] is set to false, the script will be built using chunks. This improves
16 | * performance when the script is intended for execution.
17 | */
18 | factory PayToPubKeyHashInputScript(dynamic signature, dynamic pubKey) {
19 | if (signature is TransactionSignature) signature = signature.encodeToDER();
20 | if (pubKey is KeyPair) pubKey = pubKey.publicKey;
21 | if (!(signature is Uint8List && pubKey is Uint8List))
22 | throw new ArgumentError("Unsupported input types. Read documentation.");
23 | return new PayToPubKeyHashInputScript.convert(
24 | new ScriptBuilder().data(signature).data(pubKey).build(), true);
25 | }
26 |
27 | PayToPubKeyHashInputScript.convert(Script script, [bool skipCheck = false])
28 | : super(script.program) {
29 | if (!skipCheck && !matchesType(script))
30 | throw new ScriptException("Given script is not an instance of this script type.");
31 | }
32 |
33 | TransactionSignature get signature =>
34 | new TransactionSignature.deserialize(chunks[0].data, requireCanonical: false);
35 |
36 | KeyPair get pubKey => new KeyPair.public(chunks[1].data);
37 |
38 | Address getAddress([NetworkParameters params]) => pubKey.getAddress(params);
39 |
40 | /**
41 | * Script must contain two chunks, each of which are data chunks.
42 | */
43 | static bool matchesType(Script script) {
44 | return script.chunks.length == 2 &&
45 | script.chunks[0].data.length > 1 &&
46 | script.chunks[1].data.length > 1;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/core.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.core;
2 |
3 | import "dart:collection";
4 | import "dart:typed_data";
5 | import "dart:math";
6 |
7 | import "package:cryptoutils/cryptoutils.dart";
8 |
9 | import "package:base58check/base58check.dart";
10 | import "package:bignum/bignum.dart";
11 | import "package:bytes/bytes.dart" as bytes;
12 | import "package:collection/collection.dart";
13 |
14 | import "package:pointycastle/api.dart";
15 | import "package:pointycastle/ecc/ecc_fp.dart" as fp;
16 | import "package:pointycastle/ecc/api.dart";
17 | import "package:pointycastle/signers/ecdsa_signer.dart";
18 | import "package:pointycastle/macs/hmac.dart";
19 | import "package:pointycastle/digests/sha256.dart";
20 | import "package:pointycastle/ecc/curves/secp256k1.dart";
21 |
22 | // tmp
23 | import "package:asn1lib/asn1lib.dart";
24 |
25 | import "package:bitcoin/src/utils.dart" as utils;
26 | import "package:bitcoin/src/crypto.dart" as crypto;
27 |
28 | import "package:bitcoin/script.dart";
29 | import "package:bitcoin/scripts/common.dart";
30 |
31 | import "package:bitcoin/src/wire/serialization.dart";
32 |
33 | // utils
34 | part "src/core/units.dart";
35 |
36 | // addresses and private keys
37 | part "src/core/address.dart";
38 | part "src/core/keypair.dart";
39 | part "src/core/sig_hash.dart";
40 | part "src/core/transaction_signature.dart";
41 | // private key security
42 | part "src/crypto/key_crypter.dart";
43 | part "src/crypto/key_crypter_exception.dart";
44 | part "src/crypto/encrypted_private_key.dart";
45 |
46 | // network settings
47 | part "src/core/params/network_parameters.dart";
48 | part "src/core/params/main_net_params.dart";
49 | part "src/core/params/test_net_params.dart";
50 | part "src/core/params/unit_test_params.dart";
51 |
52 | // transactions
53 | part "src/core/transaction.dart";
54 | part "src/core/transaction_input.dart";
55 | part "src/core/transaction_outpoint.dart";
56 | part "src/core/transaction_output.dart";
57 |
58 | // blocks
59 | part "src/core/block.dart";
60 | part "src/core/block_header.dart";
61 | part "src/core/verification_exception.dart";
62 |
63 | // bloom filters
64 | part "src/core/bloom_filter.dart";
65 | part "src/core/filtered_block.dart";
66 | part "src/core/partial_merkle_tree.dart";
67 |
--------------------------------------------------------------------------------
/lib/scripts/pay_to_pubkey_hash_output.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.output.pay_to_pubkey_hash;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:cryptoutils/cryptoutils.dart";
6 |
7 | import "package:bitcoin/core.dart";
8 | import "package:bitcoin/script.dart";
9 |
10 | class PayToPubKeyHashOutputScript extends Script {
11 | static const int LENGTH =
12 | 25; // OP_DUP + OP_HASH160 + 0x14 + address (20) + OP_EQUALVERIFY + OP_CHECKSIG
13 |
14 | /**
15 | * Create a new pay to address transaction output.
16 | *
17 | * If [encoded] is set to false, the script will be built using chunks. This improves
18 | * performance when the script is intended for execution.
19 | */
20 | factory PayToPubKeyHashOutputScript(Hash160 pubkeyHash) {
21 | return new PayToPubKeyHashOutputScript.convert(
22 | new ScriptBuilder()
23 | .op(ScriptOpCodes.OP_DUP)
24 | .op(ScriptOpCodes.OP_HASH160)
25 | .data(pubkeyHash.asBytes())
26 | .op(ScriptOpCodes.OP_EQUALVERIFY)
27 | .op(ScriptOpCodes.OP_CHECKSIG)
28 | .build(),
29 | true);
30 | }
31 |
32 | /**
33 | * Auxiliary constructor for paying to a regular address.
34 | */
35 | factory PayToPubKeyHashOutputScript.withAddress(Address address) =>
36 | new PayToPubKeyHashOutputScript(address.hash160);
37 |
38 | PayToPubKeyHashOutputScript.convert(Script script, [bool skipCheck = false])
39 | : super(script.program) {
40 | if (!skipCheck && !matchesType(script))
41 | throw new ScriptException("Given script is not an instance of this script type.");
42 | }
43 |
44 | Uint8List get pubkeyHash => new Uint8List.fromList(program.sublist(3, 23));
45 |
46 | Address getAddress([NetworkParameters params = NetworkParameters.MAIN_NET]) =>
47 | new Address.fromHash160(pubkeyHash, params.addressHeader);
48 |
49 | static bool matchesType(Script script) {
50 | return script.program.length == LENGTH &&
51 | script.program[0] == ScriptOpCodes.OP_DUP &&
52 | script.program[1] == ScriptOpCodes.OP_HASH160 &&
53 | script.program[2] == 0x14 &&
54 | script.program[23] == ScriptOpCodes.OP_EQUALVERIFY &&
55 | script.program[24] == ScriptOpCodes.OP_CHECKSIG;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/resources/sig_noncanonical.json:
--------------------------------------------------------------------------------
1 | [
2 | "non-hex strings are ignored",
3 |
4 | "too short:", "30050201FF020001",
5 | "too long:", "30470221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105022200002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
6 | "hashtype:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed11",
7 | "type:", "314402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
8 | "total length:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
9 | "S len oob:", "301F01205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb101",
10 | "R+S:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed0001",
11 |
12 | "R type:", "304401205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
13 | "R len = 0:", "3024020002202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
14 | "R<0:", "304402208990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
15 | "R padded:", "30450221005990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610502202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
16 |
17 |
18 | "S type:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba610501202d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
19 | "S len = 0:", "302402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba6105020001",
20 | "S<0:", "304402205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba61050220fd5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01",
21 | "S padded:", "304502205990e0584b2b238e1dfaad8d6ed69ecc1a4a13ac85fc0b31d0df395eb1ba61050221002d5876262c288beb511d061691bf26777344b702b00f8fe28621fe4e566695ed01"
22 | ]
23 |
--------------------------------------------------------------------------------
/lib/src/core/transaction_output.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class TransactionOutput extends BitcoinSerializable {
4 | int value;
5 | Script scriptPubKey;
6 |
7 | TransactionOutput({int this.value, Script this.scriptPubKey}) {
8 | if (value < -1 || value > NetworkParameters.MAX_MONEY)
9 | throw new ArgumentError("Amounts must be positive and smaller than the max value.");
10 | }
11 |
12 | factory TransactionOutput.fromBitcoinSerialization(Uint8List serialization, int pver) {
13 | var reader = new bytes.Reader(serialization);
14 | var obj = new TransactionOutput.empty();
15 | obj.bitcoinDeserialize(reader, pver);
16 | return obj;
17 | }
18 |
19 | /// Create an empty instance.
20 | TransactionOutput.empty();
21 |
22 | factory TransactionOutput.payToAddress(Address to, int amount,
23 | [Transaction parent, NetworkParameters params = NetworkParameters.MAIN_NET]) {
24 | return new TransactionOutput(
25 | value: amount, scriptPubKey: new PayToPubKeyHashOutputScript.withAddress(to));
26 | }
27 |
28 | factory TransactionOutput.payToPubKey(KeyPair key, int amount,
29 | [Transaction parent, NetworkParameters params = NetworkParameters.MAIN_NET]) {
30 | return new TransactionOutput(value: amount, scriptPubKey: new PayToPubKeyOutputScript(key));
31 | }
32 |
33 | factory TransactionOutput.payToScriptHash(Hash160 scriptHash, int amount,
34 | [Transaction parent, NetworkParameters params = NetworkParameters.MAIN_NET]) {
35 | return new TransactionOutput(
36 | value: amount, scriptPubKey: new PayToScriptHashOutputScript(scriptHash));
37 | }
38 |
39 | @override
40 | operator ==(dynamic other) {
41 | if (other.runtimeType != TransactionOutput) return false;
42 | if (identical(this, other)) return true;
43 | return value == other.value && scriptPubKey == other.scriptPubKey;
44 | }
45 |
46 | @override
47 | int get hashCode {
48 | return value.hashCode ^ scriptPubKey.hashCode;
49 | }
50 |
51 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
52 | writeUintLE(buffer, value, 8);
53 | writeByteArray(buffer, scriptPubKey.encode());
54 | }
55 |
56 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
57 | value = readUintLE(reader, 8);
58 | scriptPubKey = new Script(readByteArray(reader));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/src/core/transaction_input.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class TransactionInput extends BitcoinSerializable {
4 | static const int NO_SEQUENCE = 0xFFFFFFFF;
5 |
6 | TransactionOutPoint outpoint;
7 | Script scriptSig;
8 | int sequence;
9 |
10 | /**
11 | * Create a new [TransactionInput].
12 | *
13 | * It's not possible to specify both the [output] parameter and the [outpoint] parameter.
14 | */
15 | TransactionInput(
16 | {TransactionOutPoint this.outpoint, Script this.scriptSig, int this.sequence: NO_SEQUENCE}) {
17 | outpoint = outpoint ?? new TransactionOutPoint(index: NO_SEQUENCE);
18 | scriptSig = scriptSig ?? Script.EMPTY_SCRIPT;
19 | }
20 |
21 | /**
22 | * Create a coinbase transaction input.
23 | *
24 | * It is specified by its [TransactionOutPoint] format, but can carry any [Script] as [scriptSig].
25 | */
26 | TransactionInput.coinbase([Script this.scriptSig]) {
27 | outpoint = new TransactionOutPoint(txid: Hash256.ZERO_HASH, index: -1);
28 | scriptSig = scriptSig ?? Script.EMPTY_SCRIPT;
29 | }
30 |
31 | factory TransactionInput.fromBitcoinSerialization(Uint8List serialization, int pver) {
32 | var reader = new bytes.Reader(serialization);
33 | var obj = new TransactionInput.empty();
34 | obj.bitcoinDeserialize(reader, pver);
35 | return obj;
36 | }
37 |
38 | /// Create an empty instance.
39 | TransactionInput.empty();
40 |
41 | bool get isCoinbase {
42 | return outpoint.txid == Hash256.ZERO_HASH && (outpoint.index & 0xFFFFFFFF) == 0xFFFFFFFF;
43 | }
44 |
45 | @override
46 | operator ==(dynamic other) {
47 | if (other.runtimeType != TransactionInput) return false;
48 | if (identical(this, other)) return true;
49 | return outpoint == other.outpoint && scriptSig == other.scriptSig && sequence == other.sequence;
50 | }
51 |
52 | @override
53 | int get hashCode {
54 | return outpoint.hashCode ^ scriptSig.hashCode ^ sequence.hashCode;
55 | }
56 |
57 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
58 | writeObject(buffer, outpoint, pver);
59 | writeByteArray(buffer, scriptSig.encode());
60 | writeUintLE(buffer, sequence);
61 | }
62 |
63 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
64 | outpoint = readObject(reader, new TransactionOutPoint.empty(), pver);
65 | scriptSig = new Script(readByteArray(reader));
66 | sequence = readUintLE(reader);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/src/crypto.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.src.crypto;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:pointycastle/digests/ripemd160.dart";
6 | import "package:pointycastle/digests/sha256.dart";
7 | import "package:pointycastle/digests/sha1.dart";
8 | import "package:pointycastle/src/impl/base_digest.dart";
9 |
10 | import "package:bitcoin/src/utils.dart" as utils;
11 |
12 | class DoubleSHA256Digest extends BaseDigest {
13 | //TODO pointycastle registry
14 |
15 | SHA256Digest _internal = new SHA256Digest();
16 |
17 | DoubleSHA256Digest();
18 |
19 | @override
20 | String get algorithmName => "SHA-256d";
21 |
22 | @override
23 | int get digestSize => _internal.digestSize;
24 |
25 | @override
26 | void reset() => _internal.reset();
27 |
28 | @override
29 | void updateByte(int inp) => _internal.updateByte(inp);
30 |
31 | @override
32 | void update(Uint8List inp, int inpOff, int len) => _internal.update(inp, inpOff, len);
33 |
34 | @override
35 | int doFinal(Uint8List out, int outOff) {
36 | Uint8List firstSum = new Uint8List(digestSize);
37 | _internal.doFinal(firstSum, 0);
38 | SHA256Digest secondDigest = new SHA256Digest();
39 | secondDigest.update(firstSum, 0, firstSum.length);
40 | return secondDigest.doFinal(out, outOff);
41 | }
42 | }
43 |
44 | /**
45 | * Calculates the SHA-256 hash of the input data.
46 | */
47 | Uint8List singleDigest(Uint8List input) => new SHA256Digest().process(input);
48 |
49 | /**
50 | * Calculates the double-round SHA-256 hash of the input data.
51 | */
52 | Uint8List doubleDigest(Uint8List input) => new DoubleSHA256Digest().process(input);
53 |
54 | /**
55 | * Calculates the double-round SHA-256 hash of the input data concatenated together.
56 | */
57 | Uint8List doubleDigestTwoInputs(Uint8List input1, Uint8List input2) =>
58 | doubleDigest(utils.concatBytes(input1, input2));
59 |
60 | /**
61 | * Calculates the RIPEMD-160 hash of the given input.
62 | */
63 | Uint8List ripemd160Digest(Uint8List input) => //TODO refactor hash160
64 | new RIPEMD160Digest().process(input);
65 |
66 | /**
67 | * Calculates the SHA-1 hash of the given input.
68 | */
69 | Uint8List sha1Digest(Uint8List input) => new SHA1Digest().process(input);
70 |
71 | /**
72 | * Calculates the RIPEMD-160 hash of the SHA-256 hash of the input.
73 | * This is used to convert an ECDSA public key to a Bitcoin address.
74 | */
75 | Uint8List sha256hash160(Uint8List input) => ripemd160Digest(singleDigest(input));
76 |
--------------------------------------------------------------------------------
/lib/src/core/params/network_parameters.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | abstract class NetworkParameters {
4 | // USE THESE AS PARAMS
5 |
6 | static const NetworkParameters MAIN_NET = const _MainNetParams();
7 | static const NetworkParameters TEST_NET = const _TestNetParams();
8 |
9 | static const NetworkParameters UNIT_TEST = const _UnitTestParams();
10 |
11 | static final Map PARAMS_BY_MAGIC = {
12 | MAIN_NET.magicValue: MAIN_NET,
13 | TEST_NET.magicValue: TEST_NET,
14 | UNIT_TEST.magicValue: UNIT_TEST,
15 | };
16 |
17 | static const List SUPPORTED_PARAMS = const [MAIN_NET, TEST_NET];
18 |
19 | // GLOBAL PARAMETERS
20 |
21 | static const int PROTOCOL_VERSION = 70001;
22 | static final Uint8List SATOSHI_KEY = CryptoUtils.hexToBytes(
23 | "04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284");
24 |
25 | /**
26 | * The maximum money to be generated
27 | */
28 | static const int MAX_MONEY = 21000000 * Units.COIN;
29 |
30 | // NETWORK-SPECIFIC PARAMETERS
31 |
32 | final int addressHeader;
33 | final int p2shHeader;
34 | final int magicValue;
35 | final String id;
36 |
37 | final int port;
38 |
39 | const NetworkParameters._(
40 | {int this.addressHeader,
41 | int this.p2shHeader,
42 | int this.magicValue,
43 | String this.id,
44 | int this.port});
45 |
46 | Block get genesisBlock;
47 | BigInteger get proofOfWorkLimit;
48 | Uint8List get alertSigningKey => SATOSHI_KEY;
49 |
50 | List get acceptableAddressHeaders => [addressHeader, p2shHeader];
51 |
52 | static Block _createGenesis(NetworkParameters params) {
53 | Block genesisBlock = new Block();
54 | Transaction t = new Transaction();
55 | // A script containing the difficulty bits and the following message:
56 | //
57 | // "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
58 | Uint8List bytes = CryptoUtils.hexToBytes(
59 | "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73");
60 | t.addInput(new TransactionInput(scriptSig: new Script(bytes)));
61 | Script pubKeyScript = new ScriptBuilder()
62 | .data(CryptoUtils.hexToBytes(
63 | "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"))
64 | .op(ScriptOpCodes.OP_CHECKSIG)
65 | .build();
66 | t.addOutput(new TransactionOutput(value: Units.toSatoshi(50), scriptPubKey: pubKeyScript));
67 | genesisBlock.addTransaction(t, false);
68 | return genesisBlock;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/scripts/multisig_output.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.output.multisig;
2 |
3 | import "package:bitcoin/core.dart";
4 | import "package:bitcoin/script.dart";
5 |
6 | class MultiSigOutputScript extends Script {
7 | /**
8 | * Create a new multi-signature output script that requires at least of the given keys to sign using
9 | * OP_CHECKMULTISIG.
10 | *
11 | * Standard multisig outputs have max 3 keys, but it is possible to add up to 16 keys.
12 | *
13 | * If [encoded] is set to false, the script will be built using chunks. This improves
14 | * performance when the script is intended for execution.
15 | */
16 | factory MultiSigOutputScript(int threshold, List pubkeys) {
17 | if (threshold <= 0 || threshold > pubkeys.length)
18 | throw new ScriptException("Invalid threshold value.");
19 | if (pubkeys.length > 16) throw new ScriptException("Maximum 16 public keys.");
20 |
21 | ScriptBuilder builder = new ScriptBuilder().smallNum(threshold);
22 | pubkeys.forEach((pk) => builder.data(pk.publicKey));
23 | builder.smallNum(pubkeys.length).op(ScriptOpCodes.OP_CHECKMULTISIG);
24 | return new MultiSigOutputScript.convert(builder.build(), true);
25 | }
26 |
27 | MultiSigOutputScript.convert(Script script, [bool skipCheck = false]) : super(script.program) {
28 | if (!skipCheck && !matchesType(script))
29 | throw new ScriptException("Given script is not an instance of this script type.");
30 | }
31 |
32 | int get threshold => Script.decodeFromOpN(chunks[0].data[0]);
33 |
34 | List get pubKeys {
35 | List keys = new List();
36 | for (int i = 0; i < (chunks.length - 3); i++) {
37 | keys.add(new KeyPair.public(chunks[i + 1].data));
38 | }
39 | return keys;
40 | }
41 |
42 | static bool matchesType(Script script) {
43 | List chunks = script.chunks;
44 | // script length must be 3 + #pubkeys with max 16 pubkeys
45 | if (chunks.length < 4 || chunks.length > 19) return false;
46 | // second chunks must be OP_N code with threshold, value from 0 to 16
47 | try {
48 | if (Script.decodeFromOpN(chunks[0].opCode) < 0 ||
49 | Script.decodeFromOpN(chunks[0].data[0]) > 16) return false;
50 | } on ScriptException {
51 | // invalid OP_N
52 | return false;
53 | }
54 | // intermediate chunks must be data chunks. these are the pubkeys
55 | for (int i = 0; i < (chunks.length - 3); i++) {
56 | if (chunks[i + 1].data.length <= 1) return false;
57 | }
58 | // one but last chunk must be OP_N code with #pubkeys, must be #chunks - 1
59 | if (Script.decodeFromOpN(chunks[chunks.length - 2].data[0]) != chunks.length - 3) return false;
60 | // last chunk must be OP_MULTISIG opcode
61 | return chunks[chunks.length - 1].opCode == ScriptOpCodes.OP_CHECKMULTISIG;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/src/core/filtered_block.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class FilteredBlock extends BitcoinSerializable {
4 | /** The protocol version at which Bloom filtering started to be supported. */
5 | static const int MIN_PROTOCOL_VERSION = 70000;
6 |
7 | BlockHeader header;
8 | PartialMerkleTree merkleTree;
9 |
10 | // cached list of tx hashes
11 | List _hashes;
12 | Map _txs;
13 |
14 | FilteredBlock(BlockHeader this.header, PartialMerkleTree this.merkleTree) {
15 | if (header == null || merkleTree == null)
16 | throw new ArgumentError("header or merkleTree is null");
17 | }
18 |
19 | factory FilteredBlock.fromBitcoinSerialization(Uint8List serialization, int pver) {
20 | var reader = new bytes.Reader(serialization);
21 | var obj = new FilteredBlock.empty();
22 | obj.bitcoinDeserialize(reader, pver);
23 | return obj;
24 | }
25 |
26 | /// Create an empty instance.
27 | FilteredBlock.empty();
28 |
29 | /**
30 | * Number of transactions in this block, before it was filtered.
31 | */
32 | int get transactionCount => merkleTree.transactionCount;
33 |
34 | List get transactionHashes {
35 | if (_hashes == null) {
36 | List hashes = new List();
37 | if (header.merkleRoot == merkleTree.getTxnHashAndMerkleRoot(hashes)) {
38 | _hashes = hashes;
39 | } else {
40 | throw new Exception(
41 | "Merkle root of block header does not match merkle root of partial merkle tree.");
42 | }
43 | }
44 | return new UnmodifiableListView(_hashes);
45 | }
46 |
47 | // the following two methods are used to fill this block with relevant transactions
48 |
49 | /**
50 | * Provide this FilteredBlock with a transaction which is in its merkle tree
51 | * @returns false if the tx is not relevant to this FilteredBlock
52 | */
53 | bool provideTransaction(Transaction tx) {
54 | _txs = _txs ?? new Map();
55 | Hash256 hash = tx.hash;
56 | if (_hashes.contains(hash)) {
57 | _txs[hash] = tx;
58 | return true;
59 | } else {
60 | return false;
61 | }
62 | }
63 |
64 | /**
65 | * Gets the set of transactions which were provided using provideTransaction() which match in getTransactionHashes()
66 | */
67 | Map get associatedTransactions {
68 | return new UnmodifiableMapView(_txs); //TODO something changed in the collection package
69 | }
70 |
71 | // serialization
72 |
73 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
74 | header.bitcoinSerializeAsEmptyBlock(buffer, pver);
75 | writeObject(buffer, merkleTree, pver);
76 | }
77 |
78 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
79 | header = readObject(reader, new BlockHeader.empty(), pver);
80 | merkleTree = readObject(reader, new PartialMerkleTree.empty(), pver);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/lib/scripts/multisig_input.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.input.multisig;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:cryptoutils/cryptoutils.dart";
6 | import "package:pointycastle/api.dart";
7 |
8 | import "package:bitcoin/core.dart";
9 | import "package:bitcoin/script.dart";
10 |
11 | class MultiSigInputScript extends Script {
12 | /**
13 | * Create a script that satisfies an [OP_CHECKMULTISIG] program.
14 | */
15 | factory MultiSigInputScript(List signatures) {
16 | return new MultiSigInputScript.fromEncodedSignatures(
17 | new List.from(signatures.map((s) => s.bitcoinSerializedBytes(0))));
18 | }
19 |
20 | MultiSigInputScript.convert(Script script, [bool skipCheck = false]) : super(script.program) {
21 | if (!skipCheck && !matchesType(script))
22 | throw new ScriptException("Given script is not an instance of this script type.");
23 | }
24 |
25 | /**
26 | * Create a multisig script from signatures that have
27 | * already been encoded using the Bitcoin specification.
28 | *
29 | * No checks on the encoding are performed.
30 | */
31 | factory MultiSigInputScript.fromEncodedSignatures(List signatures) {
32 | if (signatures.length <= 0 || signatures.length > 16)
33 | throw new ScriptException("A minimum of 1 and a maximum of 16 signatures should be given.");
34 | ScriptBuilder builder = new ScriptBuilder()
35 | ..smallNum(
36 | 0); // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.
37 | signatures.forEach((s) => builder.data(s));
38 | return new MultiSigInputScript.convert(builder.build(), true);
39 | }
40 |
41 | /**
42 | * Create a multisig script from a list of keys and the [message] that they should sign.
43 | *
44 | * Use [aesKeys] to specify the decryption keys for each key (if required).
45 | * The [aesKeys] are mapped one-to-one with the [keys].
46 | */
47 | factory MultiSigInputScript.fromKeys(List keys, Hash256 message,
48 | [List aesKeys]) {
49 | if (aesKeys == null) aesKeys = new List.filled(keys.length, null);
50 | List signatures = new List(keys.length);
51 | for (int i = 0; i < keys.length; i++) signatures[i] = keys[i].sign(message, aesKeys[i]);
52 | return new MultiSigInputScript(signatures);
53 | }
54 |
55 | List get signatures =>
56 | chunks.sublist(0).map((c) => new TransactionSignature.deserialize(c.data));
57 |
58 | /**
59 | *
60 | *
61 | * It's not possible to be 100% sure that the data elements in the script are signatures.
62 | */
63 | static bool matchesType(Script script) {
64 | List chunks = script.chunks;
65 | if (chunks.length > 17 || chunks.length < 2) return false;
66 | if (chunks[0].opCode != Script.encodeToOpN(0)) return false;
67 | for (int i = 1; i < chunks.length; i++) {
68 | if (chunks[i].isOpCode) return false;
69 | }
70 | return true;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/scripts/pay_to_script_hash_output.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.scripts.output.pay_to_script_hash;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:cryptoutils/cryptoutils.dart";
6 |
7 | import "package:bitcoin/core.dart";
8 | import "package:bitcoin/script.dart";
9 |
10 | class PayToScriptHashOutputScript extends Script {
11 | /**
12 | * Create a new P2SH output script.
13 | *
14 | * If [encoded] is set to false, the script will be built using chunks. This improves
15 | * performance when the script is intended for execution.
16 | */
17 | factory PayToScriptHashOutputScript(Hash160 scriptHash) {
18 | if (scriptHash == null || scriptHash.lengthInBytes != 20)
19 | throw new ScriptException("The script hash must be of size 20!");
20 | return new PayToScriptHashOutputScript.convert(
21 | new ScriptBuilder()
22 | .op(ScriptOpCodes.OP_HASH160)
23 | .data(scriptHash.asBytes())
24 | .op(ScriptOpCodes.OP_EQUAL)
25 | .build(),
26 | true);
27 | }
28 |
29 | factory PayToScriptHashOutputScript.withAddress(Address address) =>
30 | new PayToScriptHashOutputScript(address.hash160);
31 |
32 | PayToScriptHashOutputScript.convert(Script script, [bool skipCheck = false])
33 | : super(script.program) {
34 | if (!skipCheck && !matchesType(script))
35 | throw new ScriptException("Given script is not an instance of this script type.");
36 | }
37 |
38 | Uint8List get scriptHash => new Uint8List.fromList(program.getRange(2, 22));
39 |
40 | Address getAddress([NetworkParameters params = NetworkParameters.MAIN_NET]) =>
41 | new Address.fromHash160(program.getRange(3, 23), params.p2shHeader);
42 |
43 | /**
44 | * Whether or not this is a scriptPubKey representing a pay-to-script-hash output. In such outputs, the logic that
45 | * controls reclamation is not actually in the output at all. Instead there's just a hash, and it's up to the
46 | * spending input to provide a program matching that hash. This rule is "soft enforced" by the network as it does
47 | * not exist in Satoshis original implementation. It means blocks containing P2SH transactions that don't match
48 | * correctly are considered valid, but won't be mined upon, so they'll be rapidly re-orgd out of the chain. This
49 | * logic is defined by BIP 16.
50 | *
51 | * bitcoinj does not support creation of P2SH transactions today. The goal of P2SH is to allow short addresses
52 | * even for complex scripts (eg, multi-sig outputs) so they are convenient to work with in things like QRcodes or
53 | * with copy/paste, and also to minimize the size of the unspent output set (which improves performance of the
54 | * Bitcoin system).
55 | */
56 | static bool matchesType(Script script) {
57 | return script.program.length == 23 &&
58 | script.program[0] == ScriptOpCodes.OP_HASH160 &&
59 | script.program[1] == 0x14 &&
60 | script.program[22] == ScriptOpCodes.OP_EQUAL;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/alert_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class AlertMessage extends Message {
4 | @override
5 | String get command => Message.CMD_ALERT;
6 |
7 | static const int ALERT_VERSION = 1;
8 |
9 | // Chosen arbitrarily to avoid memory blowups.
10 | static const int MAX_SET_SIZE = 100;
11 |
12 | int version; // specific version for alert messages
13 | DateTime relayUntil;
14 | DateTime expiration;
15 | int id;
16 | int cancel;
17 | Set cancelSet;
18 | int minVer;
19 | int maxVer;
20 | Set matchingSubVer;
21 | int priority;
22 | String comment;
23 | String statusBar;
24 | String reserved;
25 |
26 | Uint8List messageChecksum;
27 | ECDSASignature signature;
28 |
29 | AlertMessage();
30 |
31 | /// Create an empty instance.
32 | AlertMessage.empty();
33 |
34 | bool isSignatureValid(Uint8List key) =>
35 | KeyPair.verifySignatureForPubkey(messageChecksum, signature, key);
36 |
37 | @override
38 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
39 | Uint8List message = readByteArray(reader);
40 | signature = new ECDSASignature.fromDER(readByteArray(reader));
41 | messageChecksum = crypto.doubleDigest(message);
42 | _parseMessage(new bytes.Reader(message));
43 | }
44 |
45 | void _parseMessage(bytes.Reader reader) {
46 | version = readUintLE(reader);
47 | relayUntil = new DateTime.fromMillisecondsSinceEpoch(readUintLE(reader, 8) * 1000);
48 | expiration = new DateTime.fromMillisecondsSinceEpoch(readUintLE(reader, 8) * 1000);
49 | id = readUintLE(reader);
50 | cancel = readUintLE(reader);
51 | int cancelSetSize = readVarInt(reader);
52 | cancelSet = new HashSet();
53 | for (int i = 0; i < cancelSetSize; i++) {
54 | cancelSet.add(readUintLE(reader));
55 | }
56 | minVer = readUintLE(reader);
57 | maxVer = readUintLE(reader);
58 | int subVerSetSize = readVarInt(reader);
59 | matchingSubVer = new HashSet();
60 | for (int i = 0; i < subVerSetSize; i++) matchingSubVer.add(readVarStr(reader));
61 | priority = readUintLE(reader);
62 | comment = readVarStr(reader);
63 | statusBar = readVarStr(reader);
64 | reserved = readVarStr(reader);
65 | }
66 |
67 | @override
68 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
69 | if (signature == null) throw new Exception("Cannot sign AlertMessages ourselves");
70 | _writeMessage(buffer);
71 | writeByteArray(buffer, signature.encodeToDER());
72 | }
73 |
74 | void _writeMessage(bytes.Buffer buffer) {
75 | writeBytes(buffer, utils.uintToBytesLE(version, 4));
76 | writeBytes(buffer, utils.uintToBytesLE(relayUntil.millisecondsSinceEpoch ~/ 1000, 8));
77 | writeBytes(buffer, utils.uintToBytesLE(expiration.millisecondsSinceEpoch ~/ 1000, 8));
78 | writeBytes(buffer, utils.uintToBytesLE(id, 4));
79 | writeBytes(buffer, utils.uintToBytesLE(cancel, 4));
80 | //COMPLETE how to encode the sets?
81 | writeBytes(buffer, utils.uintToBytesLE(minVer, 4));
82 | writeBytes(buffer, utils.uintToBytesLE(maxVer, 4));
83 | //another set
84 | writeBytes(buffer, utils.uintToBytesLE(priority, 4));
85 | writeVarStr(buffer, comment);
86 | writeVarStr(buffer, statusBar);
87 | writeVarStr(buffer, reserved);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/src/wire/peer_address.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class PeerAddress extends BitcoinSerializable {
4 | static const int SERIALIZATION_SIZE = 30;
5 |
6 | Uint8List address;
7 | int port;
8 | BigInteger services;
9 | int time;
10 |
11 | /**
12 | *
13 | *
14 | * The [address] parameter should either by a [String] or [Uint8List].
15 | */
16 | PeerAddress(dynamic address, {int this.port, BigInteger this.services, int this.time}) {
17 | this.address = _formatAddress(address);
18 | services = services ?? BigInteger.ZERO;
19 | }
20 |
21 | factory PeerAddress.localhost({BigInteger services, int port}) =>
22 | new PeerAddress("127.0.0.1", port: port, services: services);
23 |
24 | factory PeerAddress.fromBitcoinSerialization(Uint8List serialization, int pver) {
25 | var reader = new bytes.Reader(serialization);
26 | var obj = new PeerAddress.empty();
27 | obj.bitcoinDeserialize(reader, pver);
28 | return obj;
29 | }
30 |
31 | /// Create an empty instance.
32 | PeerAddress.empty();
33 |
34 | static Uint8List _formatAddress(dynamic address) {
35 | if (address == null) throw new ArgumentError("The address argument should not be null");
36 | if (address is String) {
37 | try {
38 | address = new Uint8List.fromList(Uri.parseIPv6Address(address));
39 | } on FormatException {
40 | try {
41 | address = new Uint8List.fromList(Uri.parseIPv4Address(address));
42 | } on FormatException {
43 | throw new FormatException("Bad IP address format!");
44 | }
45 | }
46 | }
47 | if (address is! Uint8List) throw new ArgumentError("Invalid address parameter type");
48 | if (address.length == 4) {
49 | address = new Uint8List.fromList(new List.filled(16, 0)..setRange(12, 16, address));
50 | address[10] = 0xff;
51 | address[11] = 0xff;
52 | }
53 | if (address.length != 16)
54 | throw new ArgumentError("Invalid address length. Must be either 4 or 16 bytes long.");
55 | return address;
56 | }
57 |
58 | @override
59 | String toString() {
60 | return "[${address}]:$port";
61 | }
62 |
63 | @override
64 | bool operator ==(dynamic other) {
65 | if (other.runtimeType != PeerAddress) return false;
66 | return address == other.address &&
67 | port == other.port &&
68 | services == other.services &&
69 | time == other.time;
70 | }
71 |
72 | @override
73 | int get hashCode {
74 | return address.hashCode ^ port.hashCode ^ time.hashCode ^ services.hashCode;
75 | }
76 |
77 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
78 | if (pver >= 31402) time = readUintLE(reader);
79 | services = utils.bytesToUBigIntLE(readBytes(reader, 8));
80 | address = readBytes(reader, 16);
81 | port = (0xff & readUintLE(reader, 1)) << 8 | (0xff & readUintLE(reader, 1));
82 | }
83 |
84 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
85 | if (pver >= 31402) {
86 | // This appears to be dynamic because the client only ever sends out it's
87 | // own address so assumes itself to be up. For a fuller implementation
88 | // this needs to be dynamic only if the address refers to this client.
89 | int secs = new DateTime.now().millisecondsSinceEpoch ~/ 1000;
90 | writeUintLE(buffer, secs);
91 | }
92 | writeBytes(buffer, utils.uBigIntToBytesLE(services, 8));
93 | writeBytes(buffer, address);
94 | writeBytes(buffer, [0xFF & port >> 8]);
95 | writeBytes(buffer, [0xFF & port]);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/test/crypto/sha256hash_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.core.sha256hash;
2 |
3 | import "package:test/test.dart";
4 | import "package:cryptoutils/cryptoutils.dart";
5 |
6 | import "package:bitcoin/src/crypto.dart" as crypto;
7 |
8 | import "dart:typed_data";
9 | import "dart:convert";
10 |
11 | Map vectors = {
12 | "single": [
13 | [
14 | UTF8.encode(
15 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"),
16 | "475dbd9278ce464097f8dd241b088ac96615bfdea9e496bc05828aca94aabfca"
17 | ],
18 | [UTF8.encode("x"), "2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881"]
19 | ],
20 | "double": [
21 | [
22 | "00010966776006953D5567439E5E39F86A0D273BEE",
23 | "D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30"
24 | ],
25 | [
26 | "646572730000000000520000005d4fab8101010000006fe28c0ab6f1b372c1a6a246ae6a",
27 | "9416b146fa084df69c981de891f6c8f4ac8b9d9dedc42f6f2530a40b61e02f6a"
28 | ],
29 | [
30 | "01e215104d010000000000000000000000000000000000ffff0a000001208d",
31 | "ed52399b568ed8d59a83729c116f87d0bef284e998f3477c9861169ab12eed5c"
32 | ]
33 | ],
34 | };
35 |
36 | void main() {
37 | group("crypto.SHA-256", () {
38 | test("Hash256", () {
39 | var hashBytes = CryptoUtils.hexToBytes(vectors["single"][0][1]);
40 | var hash = new Hash256(hashBytes);
41 | expect(hash.asBytes(), equals(hashBytes));
42 | expect(hash.toHex(), equalsIgnoringCase(CryptoUtils.bytesToHex(hashBytes)));
43 | expect(hash.toString(), equalsIgnoringCase(CryptoUtils.bytesToHex(hashBytes)));
44 | var hash3 = new Hash256(hashBytes);
45 | expect(hash == hash3, isTrue);
46 | expect(hash.hashCode == hash3.hashCode, isTrue);
47 | var hash4 = new Hash256(crypto.singleDigest(new Uint8List(2)));
48 | expect(hash4 == hash, isFalse);
49 | });
50 |
51 | test("crypto.singleDigest", () {
52 | for (List vector in vectors["single"]) {
53 | var input = vector[0];
54 | var output = vector[1];
55 | if (input is String) {
56 | input = CryptoUtils.hexToBytes(input);
57 | }
58 |
59 | var hash = new Hash256(crypto.singleDigest(input));
60 | var hashString = hash.toHex();
61 | expect(hashString, equalsIgnoringCase(output));
62 | }
63 | });
64 |
65 | test("crypto.DoubleDigest", () {
66 | for (List vector in vectors["double"]) {
67 | var input = vector[0];
68 | var output = vector[1];
69 | if (input is String) {
70 | input = CryptoUtils.hexToBytes(input);
71 | }
72 |
73 | var hash = new Hash256(crypto.doubleDigest(input));
74 | var hashString = hash.toHex();
75 | expect(hashString, equalsIgnoringCase(output));
76 | }
77 | });
78 |
79 | test("DoubleSHA256Digest class", () {
80 | for (List vector in vectors["double"]) {
81 | var input = vector[0];
82 | var output = vector[1];
83 | if (input is String) {
84 | input = CryptoUtils.hexToBytes(input);
85 | }
86 |
87 | crypto.DoubleSHA256Digest digest = new crypto.DoubleSHA256Digest();
88 | digest.update(input, 0, input.length);
89 | var hash = new Uint8List(digest.digestSize);
90 | digest.doFinal(hash, 0);
91 | var hashString = CryptoUtils.bytesToHex(hash);
92 | expect(hashString, equalsIgnoringCase(output));
93 | }
94 | });
95 | });
96 | }
97 |
--------------------------------------------------------------------------------
/lib/src/core/block_header.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class BlockHeader extends BitcoinSerializable {
4 | int version;
5 | Hash256 previousBlock;
6 | Hash256 merkleRoot;
7 | int timestamp;
8 | int difficultyTarget;
9 | int nonce;
10 |
11 | Hash256 _hash;
12 |
13 | BlockHeader(
14 | {Hash256 hash,
15 | int this.version: Block.BLOCK_VERSION,
16 | Hash256 this.previousBlock,
17 | Hash256 this.merkleRoot,
18 | int this.timestamp,
19 | int this.difficultyTarget,
20 | int this.nonce: 0}) {
21 | this._hash = hash;
22 | previousBlock = previousBlock ?? Hash256.ZERO_HASH;
23 | difficultyTarget = difficultyTarget ?? Block.EASIEST_DIFFICULTY_TARGET;
24 | }
25 |
26 | factory BlockHeader.fromBitcoinSerialization(Uint8List serialization, int pver) {
27 | var reader = new bytes.Reader(serialization);
28 | var obj = new BlockHeader.empty();
29 | obj.bitcoinDeserialize(reader, pver);
30 | return obj;
31 | }
32 |
33 | /// Create an empty instance.
34 | BlockHeader.empty();
35 |
36 | /// Used only as superconstructor in Block
37 | BlockHeader._header(hash, version, previousBlock, merkleRoot, timestamp, difficultyTarget, nonce)
38 | : this(
39 | hash: hash,
40 | version: version,
41 | previousBlock: previousBlock,
42 | merkleRoot: merkleRoot,
43 | timestamp: timestamp,
44 | difficultyTarget: difficultyTarget,
45 | nonce: nonce);
46 |
47 | Hash256 get hash {
48 | if (_hash == null) {
49 | _hash = calculateHash();
50 | }
51 | return _hash;
52 | }
53 |
54 | DateTime get time => new DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
55 |
56 | void set time(DateTime time) {
57 | timestamp = time.millisecondsSinceEpoch ~/ 1000;
58 | }
59 |
60 | BigInteger get difficultyTargetAsInteger => utils.decodeCompactBits(difficultyTarget);
61 |
62 | /**
63 | * Returns the work represented by this block.
64 | *
65 | * Work is defined as the number of tries needed to solve a block in the
66 | * average case. Consider a difficulty target that covers 5% of all possible
67 | * hash values. Then the work of the block will be 20. As the target gets
68 | * lower, the amount of work goes up.
69 | */
70 | BigInteger get work => Block._LARGEST_HASH / (difficultyTargetAsInteger + BigInteger.ONE);
71 |
72 | Hash256 calculateHash() {
73 | var buffer = new bytes.Buffer();
74 | _serializeHeaderOnly(buffer);
75 | Uint8List checksum = crypto.doubleDigest(buffer.asBytes());
76 | return new Hash256(utils.reverseBytes(checksum));
77 | }
78 |
79 | @override
80 | bool operator ==(dynamic other) {
81 | if (other.runtimeType != BlockHeader) return false;
82 | if (identical(this, other)) return true;
83 | return hash == other.hash;
84 | }
85 |
86 | @override
87 | int get hashCode => (BlockHeader).hashCode ^ hash.hashCode;
88 |
89 | void bitcoinSerialize(bytes.Buffer buffer, int pver) => _serializeHeaderOnly(buffer);
90 |
91 | void _serializeHeaderOnly(bytes.Buffer buffer) {
92 | writeUintLE(buffer, version);
93 | writeSHA256(buffer, previousBlock);
94 | writeSHA256(buffer, merkleRoot);
95 | writeUintLE(buffer, timestamp);
96 | writeUintLE(buffer, difficultyTarget);
97 | writeUintLE(buffer, nonce);
98 | }
99 |
100 | void bitcoinSerializeAsEmptyBlock(bytes.Buffer buffer, int pver) {
101 | _serializeHeaderOnly(buffer);
102 | buffer.addByte(0);
103 | }
104 |
105 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
106 | version = readUintLE(reader);
107 | previousBlock = readSHA256(reader);
108 | merkleRoot = readSHA256(reader);
109 | timestamp = readUintLE(reader);
110 | difficultyTarget = readUintLE(reader);
111 | nonce = readUintLE(reader);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/src/wire/serialization.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.wire.serialization;
2 |
3 | import "dart:convert";
4 | import "dart:typed_data";
5 |
6 | import "package:bytes/bytes.dart" as bytes;
7 | import "package:cryptoutils/cryptoutils.dart";
8 |
9 | import "package:bitcoin/src/utils.dart" as utils;
10 |
11 | /// This interface defines the minimal functions required to support Bitcoin serialization.
12 | abstract class BitcoinSerializable {
13 | void bitcoinDeserialize(bytes.Reader reader, int pver);
14 |
15 | void bitcoinSerialize(bytes.Buffer buffer, int pver);
16 |
17 | Uint8List bitcoinSerializedBytes(int pver) {
18 | var buffer = new bytes.Buffer();
19 | bitcoinSerialize(buffer, pver);
20 | return buffer.asBytes();
21 | }
22 | }
23 |
24 | //TODO rename to BitcoinSerializationException
25 | class SerializationException implements Exception {
26 | final String message;
27 |
28 | SerializationException([String this.message]);
29 |
30 | @override
31 | String toString() => "SerializationException: $message";
32 |
33 | @override
34 | bool operator ==(dynamic other) {
35 | if (other.runtimeType != SerializationException) return false;
36 | return message == other.message;
37 | }
38 | }
39 |
40 | /////////////
41 | // READING //
42 | /////////////
43 |
44 | Uint8List readBytes(bytes.Reader reader, int length) {
45 | return reader.readBytes(length);
46 | }
47 |
48 | int readUintLE(bytes.Reader reader, [int length = 4]) {
49 | int result = utils.bytesToUintLE(readBytes(reader, length), length);
50 | return result;
51 | }
52 |
53 | /// In the Bitcoin protocol, hashes are serialized in little endian.
54 | Hash256 readSHA256(bytes.Reader reader) {
55 | return new Hash256(utils.reverseBytes(readBytes(reader, 32)));
56 | }
57 |
58 | int readVarInt(bytes.Reader reader) {
59 | int firstByte = readUintLE(reader, 1);
60 | if (firstByte == 0xfd) {
61 | return readUintLE(reader, 2);
62 | }
63 | if (firstByte == 0xfe) {
64 | return readUintLE(reader, 4);
65 | }
66 | if (firstByte == 0xff) {
67 | return readUintLE(reader, 8);
68 | }
69 | return firstByte;
70 | }
71 |
72 | Uint8List readByteArray(bytes.Reader reader) {
73 | int size = readVarInt(reader);
74 | return readBytes(reader, size);
75 | }
76 |
77 | String readVarStr(bytes.Reader reader) {
78 | return UTF8.decode(readByteArray(reader));
79 | }
80 |
81 | BitcoinSerializable readObject(
82 | bytes.Reader reader, BitcoinSerializable obj, int pver) {
83 | obj.bitcoinDeserialize(reader, pver);
84 | return obj;
85 | }
86 |
87 | /////////////
88 | // WRITING //
89 | /////////////
90 |
91 | void writeBytes(bytes.Buffer buffer, List bytes) {
92 | buffer.add(bytes);
93 | }
94 |
95 | void writeUintLE(bytes.Buffer buffer, int value, [int length = 4]) {
96 | writeBytes(buffer, utils.uintToBytesLE(value, length));
97 | }
98 |
99 | /// In the Bitcoin protocol, hashes are serialized in little endian.
100 | void writeSHA256(bytes.Buffer buffer, Hash256 hash) {
101 | writeBytes(buffer, utils.reverseBytes(hash.asBytes()));
102 | }
103 |
104 | void writeVarInt(bytes.Buffer buffer, int value) {
105 | if (value < 0xfd) {
106 | writeBytes(buffer, [value]);
107 | } else if (value <= 0xffff) {
108 | writeBytes(buffer, [0xfd]);
109 | writeUintLE(buffer, value, 2);
110 | } else if (value <= 0xffffffff) {
111 | writeBytes(buffer, [0xfe]);
112 | writeUintLE(buffer, value, 4);
113 | } else {
114 | writeBytes(buffer, [0xff]);
115 | writeUintLE(buffer, value, 8);
116 | }
117 | }
118 |
119 | void writeByteArray(bytes.Buffer buffer, Uint8List bytes) {
120 | writeVarInt(buffer, bytes.length);
121 | writeBytes(buffer, bytes);
122 | }
123 |
124 | void writeVarStr(bytes.Buffer buffer, String string) {
125 | writeByteArray(buffer, UTF8.encode(string));
126 | }
127 |
128 | void writeObject(bytes.Buffer buffer, BitcoinSerializable obj, int pver) {
129 | obj.bitcoinSerialize(buffer, pver);
130 | }
131 |
--------------------------------------------------------------------------------
/test/core/address_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.core.address;
2 |
3 | import "package:test/test.dart";
4 | import "package:cryptoutils/cryptoutils.dart";
5 |
6 | import "package:bitcoin/core.dart";
7 |
8 | import "dart:typed_data";
9 |
10 | final NetworkParameters _testParams = NetworkParameters.TEST_NET;
11 | final NetworkParameters _mainParams = NetworkParameters.MAIN_NET;
12 |
13 | void main() {
14 | group("core.Address", () {
15 | test("stringification", () {
16 | // Test a testnet address.
17 | Address a = new Address.fromHash160(
18 | CryptoUtils.hexToBytes("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"),
19 | _testParams.addressHeader);
20 | expect(a.toString(), equals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv"));
21 | expect(a.address, equals("n4eA2nbYqErp7H6jebchxAN59DmNpksexv"));
22 | expect(a.isP2SHAddress(_testParams), isFalse);
23 |
24 | Address b = new Address.fromHash160(
25 | CryptoUtils.hexToBytes("4a22c3c4cbb31e4d03b15550636762bda0baf85a"),
26 | _mainParams.addressHeader);
27 | expect(b.toString(), equals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL"));
28 | expect(b.address, equals("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL"));
29 | expect(b.isP2SHAddress(_mainParams), isFalse);
30 | });
31 |
32 | test("decoding", () {
33 | Address a = new Address("n4eA2nbYqErp7H6jebchxAN59DmNpksexv");
34 | expect(CryptoUtils.bytesToHex(a.hash160.asBytes()),
35 | equals("fda79a24e50ff70ff42f7d89585da5bd19d9e5cc"));
36 |
37 | Address b = new Address("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL");
38 | expect(CryptoUtils.bytesToHex(b.hash160.asBytes()),
39 | equals("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
40 | });
41 |
42 | test("errorPaths", () {
43 | // Check what happens if we try and decode garbage.
44 | try {
45 | new Address("this is not a valid address!");
46 | fail("should not work");
47 | } on FormatException {
48 | // success
49 | } catch (e) {
50 | fail("wrong exception: $e");
51 | }
52 |
53 | // Check the empty case.
54 | try {
55 | new Address("");
56 | fail("should not work");
57 | } on FormatException {
58 | // success
59 | } catch (e) {
60 | fail("wrong exception: $e");
61 | }
62 | });
63 |
64 | test("getNetwork", () {
65 | NetworkParameters params = new Address("17kzeh4N8g49GFvdDzSf8PjaPfyoD1MndL").findNetwork();
66 | expect(params.id, _mainParams.id);
67 | params = new Address("n4eA2nbYqErp7H6jebchxAN59DmNpksexv").findNetwork();
68 | expect(params.id, _testParams.id);
69 | });
70 |
71 | test("p2shAddress", () {
72 | // Test that we can construct P2SH addresses
73 | Address mainNetP2SHAddress = new Address("35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU");
74 | expect(mainNetP2SHAddress.version, equals(_mainParams.p2shHeader));
75 | expect(mainNetP2SHAddress.isP2SHAddress(_mainParams), isTrue);
76 | Address testNetP2SHAddress = new Address("2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe");
77 | expect(testNetP2SHAddress.version, equals(_testParams.p2shHeader));
78 | expect(testNetP2SHAddress.isP2SHAddress(_testParams), isTrue);
79 |
80 | // Test that we can determine what network a P2SH address belongs to
81 | NetworkParameters mainNetParams =
82 | new Address("35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU").findNetwork();
83 | expect(mainNetParams.id, _mainParams.id);
84 | NetworkParameters testNetParams =
85 | new Address("2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe").findNetwork();
86 | expect(testNetParams.id, _testParams.id);
87 |
88 | // Test that we can convert them from hashes
89 | Uint8List hex = CryptoUtils.hexToBytes("2ac4b0b501117cc8119c5797b519538d4942e90e");
90 | Address a = new Address.p2sh(hex, _mainParams);
91 | expect(a.toString(), equals("35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU"));
92 | Address b = new Address.p2sh(
93 | CryptoUtils.hexToBytes("18a0e827269b5211eb51a4af1b2fa69333efa722"), _testParams);
94 | expect(b.toString(), equals("2MuVSxtfivPKJe93EC1Tb9UhJtGhsoWEHCe"));
95 | });
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/test/wire/bloom_filter_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.wire.bloom_filter;
2 |
3 | import "package:cryptoutils/cryptoutils.dart";
4 |
5 | import "package:test/test.dart";
6 |
7 | import "package:bitcoin/core.dart";
8 |
9 | void main() {
10 | group("wire.BloomFilter", () {
11 | test("insertSerialize", () {
12 | BloomFilter filter = new BloomFilter(3, 0.01, 0, BloomUpdate.UPDATE_ALL);
13 |
14 | filter.insert(CryptoUtils.hexToBytes("99108ad8ed9bb6274d3980bab5a85c048f0950c8"));
15 | expect(filter.contains(CryptoUtils.hexToBytes("99108ad8ed9bb6274d3980bab5a85c048f0950c8")),
16 | isTrue);
17 | // One bit different in first byte
18 | expect(filter.contains(CryptoUtils.hexToBytes("19108ad8ed9bb6274d3980bab5a85c048f0950c8")),
19 | isFalse);
20 |
21 | filter.insert(CryptoUtils.hexToBytes("b5a2c786d9ef4658287ced5914b37a1b4aa32eee"));
22 | expect(filter.contains(CryptoUtils.hexToBytes("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")),
23 | isTrue);
24 |
25 | filter.insert(CryptoUtils.hexToBytes("b9300670b4c5366e95b2699e8b18bc75e5f729c5"));
26 | expect(filter.contains(CryptoUtils.hexToBytes("b9300670b4c5366e95b2699e8b18bc75e5f729c5")),
27 | isTrue);
28 |
29 | // Value generated by the reference client
30 | expect(CryptoUtils.bytesToHex(filter.bitcoinSerializedBytes(0)),
31 | equals("03614e9b050000000000000001"));
32 | });
33 |
34 | test("insertSerializeWithTweak", () {
35 | BloomFilter filter = new BloomFilter(3, 0.01, 2147483649);
36 |
37 | filter.insert(CryptoUtils.hexToBytes("99108ad8ed9bb6274d3980bab5a85c048f0950c8"));
38 | expect(filter.contains(CryptoUtils.hexToBytes("99108ad8ed9bb6274d3980bab5a85c048f0950c8")),
39 | isTrue);
40 | // One bit different in first byte
41 | expect(filter.contains(CryptoUtils.hexToBytes("19108ad8ed9bb6274d3980bab5a85c048f0950c8")),
42 | isFalse);
43 |
44 | filter.insert(CryptoUtils.hexToBytes("b5a2c786d9ef4658287ced5914b37a1b4aa32eee"));
45 | expect(filter.contains(CryptoUtils.hexToBytes("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")),
46 | isTrue);
47 |
48 | filter.insert(CryptoUtils.hexToBytes("b9300670b4c5366e95b2699e8b18bc75e5f729c5"));
49 | expect(filter.contains(CryptoUtils.hexToBytes("b9300670b4c5366e95b2699e8b18bc75e5f729c5")),
50 | isTrue);
51 |
52 | // Value generated by the reference client
53 | expect(CryptoUtils.bytesToHex(filter.bitcoinSerializedBytes(0)),
54 | equals("03ce4299050000000100008002"));
55 | });
56 |
57 | // test("wallet", () {
58 | // NetworkParameters params = MainNetParams.get();
59 | //
60 | // DumpedPrivateKey privKey = new DumpedPrivateKey(params, "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C");
61 | //
62 | // Address addr = privKey.getKey().toAddress(params);
63 | // assertTrue(addr.toString().equals("17Wx1GQfyPTNWpQMHrTwRSMTCAonSiZx9e"));
64 | //
65 | // Wallet wallet = new Wallet(params);
66 | // // Check that the wallet was created with no keys
67 | // // If wallets ever get created with keys, this test needs redone.
68 | // for (ECKey key : wallet.getKeys())
69 | // fail();
70 | // wallet.addKey(privKey.getKey());
71 | // // Add a random key which happens to have been used in a recent generation
72 | // wallet.addKey(new ECKey(null, CryptoUtils.hexToBytes("03cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99")));
73 | // wallet.commitTx(new Transaction(params, CryptoUtils.hexToBytes("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d038754030114062f503253482fffffffff01c05e559500000000232103cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99ac00000000")));
74 | //
75 | // // We should have 2 per pubkey, and one for the pay-2-pubkey output we have
76 | // assertTrue(wallet.getBloomFilterElementCount() == 5);
77 | //
78 | // BloomFilter filter = wallet.getBloomFilter(wallet.getBloomFilterElementCount(), 0.001, 0);
79 | //
80 | // // Value generated by the reference client
81 | // assertTrue(Arrays.equals(CryptoUtils.hexToBytes("082ae5edc8e51d4a03080000000000000002"), filter.bitcoinSerialize()));
82 | // });
83 | });
84 | }
85 |
--------------------------------------------------------------------------------
/test/wire/messages/version_message_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.wire.version_message;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:bignum/bignum.dart";
6 | import "package:bytes/bytes.dart";
7 | import "package:cryptoutils/cryptoutils.dart";
8 |
9 | import "package:test/test.dart";
10 |
11 | import "package:bitcoin/core.dart";
12 | import "package:bitcoin/wire.dart";
13 |
14 | void main() {
15 | group("wire.messages.VersionMessage", () {
16 | test("decode", () {
17 | VersionMessage ver = new VersionMessage.empty();
18 | ver.bitcoinDeserialize(
19 | new Reader(CryptoUtils.hexToBytes(
20 | "71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f0004000000")),
21 | 0);
22 | expect(ver.relayBeforeFilter, isFalse);
23 | expect(ver.lastHeight, equals(1024));
24 | expect(ver.subVer, equals("/BitCoinJ:0.7-SNAPSHOT/"));
25 |
26 | ver.bitcoinDeserialize(
27 | new Reader(CryptoUtils.hexToBytes(
28 | "71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f00040000")),
29 | 0);
30 | expect(ver.relayBeforeFilter, isTrue);
31 | expect(ver.lastHeight, equals(1024));
32 | expect(ver.subVer, equals("/BitCoinJ:0.7-SNAPSHOT/"));
33 |
34 | ver.bitcoinDeserialize(
35 | new Reader(CryptoUtils.hexToBytes(
36 | "71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000172f426974436f696e4a3a302e372d534e415053484f542f")),
37 | 0);
38 | expect(ver.relayBeforeFilter, isTrue);
39 | expect(ver.lastHeight, equals(0));
40 | expect(ver.subVer, equals("/BitCoinJ:0.7-SNAPSHOT/"));
41 |
42 | ver.bitcoinDeserialize(
43 | new Reader(CryptoUtils.hexToBytes(
44 | "71110100000000000000000048e5e95000000000000000000000000000000000000000000000ffff7f000001479d000000000000000000000000000000000000ffff7f000001479d0000000000000000")),
45 | 0);
46 | expect(ver.relayBeforeFilter, isTrue);
47 | expect(ver.lastHeight, equals(0));
48 | expect(ver.subVer, equals(""));
49 | });
50 |
51 | test("bothways", () {
52 | // BigInteger this.services,
53 | // int this.time: 0,
54 | // PeerAddress this.myAddress,
55 | // PeerAddress this.theirAddress,
56 | // int this.nonce: 0,
57 | // String this.subVer,
58 | // int this.lastHeight: 0,
59 | // bool this.relayBeforeFilter: false,
60 | //
61 | var clientVersion = NetworkParameters.PROTOCOL_VERSION;
62 | var services = BigInteger.ONE;
63 | var time = new DateTime.now().millisecondsSinceEpoch ~/ 1000;
64 | var myAddress = new PeerAddress.localhost(services: services, port: 8333);
65 | var theirAddress = new PeerAddress("192.168.3.3", port: 8333, services: services, time: time);
66 | var nonce = 12321;
67 | var subVer = VersionMessage.LIBRARY_SUBVER;
68 | var lastHeight = 12345;
69 | var relay = false;
70 |
71 | VersionMessage ver = new VersionMessage(
72 | clientVersion: clientVersion,
73 | services: services,
74 | time: time,
75 | myAddress: myAddress,
76 | theirAddress: theirAddress,
77 | nonce: nonce,
78 | subVer: subVer,
79 | lastHeight: lastHeight,
80 | relayBeforeFilter: relay);
81 | Uint8List bytes = ver.bitcoinSerializedBytes(clientVersion);
82 | VersionMessage newVer = new VersionMessage.empty();
83 | newVer.bitcoinDeserialize(new Reader(bytes), clientVersion);
84 |
85 | expect(newVer.clientVersion, equals(clientVersion));
86 | expect(newVer.services, equals(services));
87 | expect(newVer.time, equals(time));
88 | expect(newVer.myAddress.address, equals(myAddress.address));
89 | expect(newVer.theirAddress.address, equals(theirAddress.address));
90 | expect(newVer.nonce, equals(nonce));
91 | expect(newVer.subVer, equals(subVer));
92 | expect(newVer.lastHeight, equals(lastHeight));
93 | expect(newVer.relayBeforeFilter, equals(relay));
94 | });
95 | });
96 | }
97 |
--------------------------------------------------------------------------------
/test/core/utils_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.core.utils;
2 |
3 | import "package:test/test.dart";
4 | import "package:cryptoutils/cryptoutils.dart";
5 |
6 | import "package:bitcoin/core.dart";
7 | import "package:bitcoin/src/crypto.dart" as crypto;
8 | import "package:bitcoin/src/utils.dart" as utils;
9 |
10 | import "dart:convert";
11 | import "dart:typed_data";
12 |
13 | void main() {
14 | group("core.Utils", () {
15 | test("singledigest", () {
16 | var _testString1 = new Uint8List.fromList(new Utf8Encoder().convert(
17 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
18 | var _testHash1 = CryptoUtils
19 | .hexToBytes("475dbd9278ce464097f8dd241b088ac96615bfdea9e496bc05828aca94aabfca");
20 | expect(crypto.singleDigest(_testString1), equals(_testHash1));
21 | });
22 |
23 | test("doubledigest", () {
24 | var _testBytes3 = CryptoUtils.hexToBytes("00010966776006953D5567439E5E39F86A0D273BEE");
25 | var _testDoubleHash3 = CryptoUtils
26 | .hexToBytes("D61967F63C7DD183914A4AE452C9F6AD5D462CE3D277798075B107615C1A8A30");
27 | expect(crypto.doubleDigest(_testBytes3), equals(_testDoubleHash3));
28 | });
29 |
30 | test("ripemd160", () {
31 | var bytes = CryptoUtils
32 | .hexToBytes("600FFE422B4E00731A59557A5CCA46CC183944191006324A447BDB2D98D4B408");
33 | var hash = CryptoUtils.hexToBytes("010966776006953D5567439E5E39F86A0D273BEE");
34 | expect(crypto.ripemd160Digest(bytes), equals(hash));
35 | });
36 |
37 | test("sha1", () {
38 | var message = new Uint8List.fromList(new Utf8Encoder().convert("test-dartcoin"));
39 | var hash = CryptoUtils.hexToBytes("7db8dc1e20c72e5f7db948bcacec8c1503fbbe1c");
40 | expect(crypto.sha1Digest(message), equals(hash));
41 | });
42 |
43 | test("sha256hash160", () {
44 | var input = CryptoUtils.hexToBytes(
45 | "0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6");
46 | var hash = CryptoUtils.hexToBytes("010966776006953D5567439E5E39F86A0D273BEE");
47 | expect(crypto.sha256hash160(input), equals(hash));
48 | });
49 |
50 | test("hexToBytes", () {
51 | // bytesToHex is a dart native function, so we can use it to test the reverse operation
52 | var byteString =
53 | "0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38855ED6F2EE187E9C582BA6";
54 | var bytes = CryptoUtils.hexToBytes(byteString);
55 | expect(CryptoUtils.bytesToHex(bytes), equalsIgnoringCase(byteString));
56 | });
57 |
58 | test("isHexString", () {
59 | expect(utils.isHexString("11"), isTrue, reason: "11");
60 | expect(utils.isHexString(CryptoUtils.bytesToHex(utils.utf8Encode("Steven"))), isTrue,
61 | reason: "steven to utf8");
62 | expect(utils.isHexString(" abd DFB109"), isTrue, reason: " abd DFB109");
63 | expect(utils.isHexString("Steven"), isFalse, reason: "Steven");
64 | });
65 |
66 | test("formatMessageForSigning", () {
67 | //TODO
68 | });
69 |
70 | test("equallists", () {
71 | var hash = CryptoUtils.hexToBytes("010966776006953D5567439E5E39F86A0D273BEE");
72 | var hash2 = CryptoUtils.hexToBytes("010966776006953D5567439E5E39F86A0D273BEE");
73 | var hash3 = CryptoUtils
74 | .hexToBytes("475dbd9278ce464097f8dd241b088ac96615bfdea9e496bc05828aca94aabfca");
75 | var list1 = [new Hash256(hash3), new Hash256(hash3)];
76 | var list2 = [new Hash256(hash3), new Hash256(hash3)];
77 | expect(utils.equalLists(hash, hash2), isTrue);
78 | expect(utils.equalLists(hash, hash), isTrue);
79 | expect(utils.equalLists(hash, hash3), isFalse);
80 | expect(utils.equalLists(list1, list2), isTrue);
81 | });
82 |
83 | test("bigintToBytes", () {
84 | //TODO
85 | });
86 |
87 | test("uintToBytes", () {
88 | //TODO all variants
89 | });
90 |
91 | //test("ipv6encoding", () => _ipv6EncodingTest());
92 | // bitcoinj
93 | test("toSatoshi", () {
94 | // String version
95 | expect(Units.toSatoshi(0.01), equals(Units.CENT));
96 | expect(Units.toSatoshi(1E-2), equals(Units.CENT));
97 | expect(Units.toSatoshi(1.01), equals(Units.COIN + Units.CENT));
98 | expect(Units.toSatoshi(21000000), equals(NetworkParameters.MAX_MONEY));
99 | });
100 |
101 | test("reverseBytes", () {
102 | expect(utils.reverseBytes(new Uint8List.fromList([5, 4, 3, 2, 1])),
103 | equals(new Uint8List.fromList([1, 2, 3, 4, 5])));
104 | });
105 | });
106 | }
107 |
--------------------------------------------------------------------------------
/lib/src/core/address.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class Address {
4 | static const int LENGTH = 20; // bytes (= 160 bits)
5 | static final _b58c = new Base58CheckCodec.bitcoin();
6 |
7 | int _version;
8 | Hash160 _hash160;
9 |
10 | /// Create a new address object from a Base58 string.
11 | ///
12 | /// Checksum will be verified.
13 | Address(String base58address) {
14 | if (version != null)
15 | throw new ArgumentError(
16 | "Version should not be passed when address is a String");
17 |
18 | // extract the payload and verify the checksum
19 | Base58CheckPayload payload = _b58c.decode(base58address);
20 | if (payload.payload.length != 20)
21 | throw new FormatException(
22 | "The Base58 address should be exactly 25 bytes long: a 21-byte "
23 | "payload and a 4-byte checksum. (Was ${payload.payload.length}");
24 |
25 | _version = payload.version;
26 | _hash160 = new Hash160(payload.payload);
27 | return;
28 | }
29 |
30 | /// Create a new Address with a 20-byte [Uint8List] or a [Hash160].
31 | Address.fromHash160(dynamic hash160, int version) {
32 | if (hash160 is Uint8List) {
33 | if (hash160.length != 20)
34 | throw new ArgumentError("To create an address from a hash160 payload, "
35 | "input needs to be exactly 20 bytes.");
36 | hash160 = new Hash160(hash160);
37 | }
38 | if (hash160 is Hash160) {
39 | _hash160 = hash160;
40 | _version = version;
41 | return;
42 | }
43 | throw new ArgumentError("Invalid arguments, please read documentation.");
44 | }
45 |
46 | /// Create a new address from a pay-to-pubkey-hash (P2PKH) hash.
47 | ///
48 | /// To create an address from a P2SH script, use the
49 | /// [PayToPubKeyHashOutputScript] class.
50 | factory Address.p2pkh(dynamic hash160,
51 | [NetworkParameters params = NetworkParameters.MAIN_NET]) =>
52 | new Address.fromHash160(hash160, params.addressHeader);
53 |
54 | /// Create an address from a pay-to-script-hash (P2SH) hash.
55 | ///
56 | /// To create an address from a P2SH script, use the
57 | /// [PayToScriptHashOutputScript] class.
58 | factory Address.p2sh(dynamic hash160,
59 | [NetworkParameters params = NetworkParameters.MAIN_NET]) =>
60 | new Address.fromHash160(hash160, params.p2shHeader);
61 |
62 | int get version => _version;
63 |
64 | Hash160 get hash160 => _hash160;
65 |
66 | /// Returns the base58 string representation of this address.
67 | String get address => _b58c.encode(
68 | new Base58CheckPayload(_version, _hash160.asBytes()));
69 |
70 | /// Finds the [NetworkParameters] that correspond to the version byte of
71 | /// this [Address].
72 | ///
73 | /// Returns [null] if no matching params are found.
74 | NetworkParameters findNetwork() {
75 | for (NetworkParameters params in NetworkParameters.SUPPORTED_PARAMS) {
76 | if (_isAcceptableVersion(params, _version)) return params;
77 | }
78 | return null;
79 | }
80 |
81 | // *******************
82 | // ** address types **
83 | // *******************
84 |
85 | /// Checks if this address is a regular pay-to-pubkey-hash address.
86 | bool isPayToPubkeyHashAddress(
87 | [NetworkParameters params = NetworkParameters.MAIN_NET]) =>
88 | _version == params.addressHeader;
89 |
90 | /// Checks if this address is a pay-to-script-hash address.
91 | bool isP2SHAddress([NetworkParameters params = NetworkParameters.MAIN_NET]) {
92 | // I first placed this check in the [PayToScriptHashOutputScript] class,
93 | // like I did with the [matchesType()] methods in the standard Scripts,
94 | // but then decided to place it here anyways.
95 | // I'm no big fan of putting BIP- or feature-specific code aspects in general classes.
96 | return _version == params.p2shHeader;
97 | }
98 |
99 | @override
100 | String toString() => address;
101 |
102 | @override
103 | bool operator ==(dynamic other) {
104 | if (other.runtimeType != Address) return false;
105 | return _version == other._version && _hash160 == other._hash160;
106 | }
107 |
108 | @override
109 | int get hashCode =>
110 | _version.hashCode ^ utils.listHashCode(_hash160.asBytes());
111 |
112 | static bool _isAcceptableVersion(NetworkParameters params, int version) =>
113 | params.acceptableAddressHeaders.contains(version);
114 | }
115 |
116 | class WrongNetworkException implements Exception {
117 | final int version;
118 | final List acceptableVersions;
119 | WrongNetworkException(int this.version, List this.acceptableVersions);
120 | String get message =>
121 | "Version code of address did not match acceptable versions for network: $version not in $acceptableVersions";
122 | @override
123 | String toString() => message;
124 | }
125 |
--------------------------------------------------------------------------------
/lib/src/core/transaction_signature.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | class TransactionSignature extends ECDSASignature {
4 | int sigHashFlags;
5 |
6 | /**
7 | * Create a new TransactionSignature from an ECDSASignature.
8 | *
9 | * It is possible to either set the sigHashFlags byte itself or
10 | * specify a SigHash value and the anyoneCanPay bool.
11 | * When nothing is specified, the default SigHash.ALL and !anyoneCanPay settings are used.
12 | */ //TODO fix serialization. is not required here, so use BitcoinSerializable instead
13 | TransactionSignature(ECDSASignature signature,
14 | {SigHash mode, bool anyoneCanPay, int this.sigHashFlags})
15 | : super(signature.r, signature.s) {
16 | if (sigHashFlags != null) {
17 | if (mode != null || anyoneCanPay != null)
18 | throw new ArgumentError(
19 | "Please specify either the sigHashFlags byte or mode + anyoneCanPay, not both.");
20 | } else {
21 | _setSigHashFlags(
22 | mode == null ? mode : SigHash.ALL, anyoneCanPay == null ? anyoneCanPay : false);
23 | }
24 | }
25 |
26 | // no lazy deserialization
27 | factory TransactionSignature.deserialize(Uint8List bytes, {bool requireCanonical: false}) {
28 | if (requireCanonical && !isEncodingCanonical(bytes)) {
29 | throw new SerializationException("Signature is not canonical");
30 | }
31 | return new TransactionSignature(new ECDSASignature.fromDER(bytes.sublist(0, bytes.length - 1)),
32 | sigHashFlags: bytes.last);
33 | }
34 |
35 | //TODO remove?
36 | factory TransactionSignature.dummy() {
37 | ECDSASignature sig = new ECDSASignature(KeyPair.HALF_CURVE_ORDER, KeyPair.HALF_CURVE_ORDER);
38 | return new TransactionSignature(sig);
39 | }
40 |
41 | void _setSigHashFlags(SigHash mode, bool anyoneCanPay) {
42 | sigHashFlags = SigHash.sigHashFlagsValue(mode, anyoneCanPay);
43 | }
44 |
45 | SigHash get sigHashMode {
46 | int mode = sigHashFlags & 0x1f;
47 | if (mode == SigHash.NONE.value)
48 | return SigHash.NONE;
49 | else if (mode == SigHash.SINGLE.value)
50 | return SigHash.SINGLE;
51 | else
52 | return SigHash.ALL;
53 | }
54 |
55 | /**
56 | * Returns true if the given signature is has canonical encoding, and will thus be accepted as standard by
57 | * the reference client. DER and the SIGHASH encoding allow for quite some flexibility in how the same structures
58 | * are encoded, and this can open up novel attacks in which a man in the middle takes a transaction and then
59 | * changes its signature such that the transaction hash is different but it's still valid. This can confuse wallets
60 | * and generally violates people's mental model of how Bitcoin should work, thus, non-canonical signatures are now
61 | * not relayed by default.
62 | */
63 | // copied from bitcoinj
64 | static bool isEncodingCanonical(Uint8List signature) {
65 | // See reference client's IsCanonicalSignature, https://bitcointalk.org/index.php?topic=8392.msg127623#msg127623
66 | // A canonical signature exists of: <30> <02> <02>
67 | // Where R and S are not negative (their first byte has its highest bit not set), and not
68 | // excessively padded (do not start with a 0 byte, unless an otherwise negative number follows,
69 | // in which case a single 0 byte is necessary and even required).
70 | if (signature.length < 9 || signature.length > 73) return false;
71 |
72 | int hashType = signature[signature.length - 1] & ((~SigHash.ANYONE_CAN_PAY));
73 | if (hashType < (SigHash.ALL.value) || hashType > (SigHash.SINGLE.value)) return false;
74 |
75 | // "wrong type" "wrong length marker"
76 | if ((signature[0] & 0xff) != 0x30 || (signature[1] & 0xff) != signature.length - 3)
77 | return false;
78 |
79 | int lenR = signature[3] & 0xff;
80 | if (5 + lenR >= signature.length || lenR == 0) return false;
81 | int lenS = signature[5 + lenR] & 0xff;
82 | if (lenR + lenS + 7 != signature.length || lenS == 0) return false;
83 |
84 | // R value type mismatch R value negative
85 | if (signature[4 - 2] != 0x02 || (signature[4] & 0x80) == 0x80) return false;
86 | if (lenR > 1 && signature[4] == 0x00 && (signature[4 + 1] & 0x80) != 0x80)
87 | return false; // R value excessively padded
88 |
89 | // S value type mismatch S value negative
90 | if (signature[6 + lenR - 2] != 0x02 || (signature[6 + lenR] & 0x80) == 0x80) return false;
91 | if (lenS > 1 && signature[6 + lenR] == 0x00 && (signature[6 + lenR + 1] & 0x80) != 0x80)
92 | return false; // S value excessively padded
93 |
94 | return true;
95 | }
96 |
97 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
98 | writeBytes(buffer, encodeToDER());
99 | writeBytes(buffer, [sigHashFlags]);
100 | }
101 |
102 | Uint8List bitcoinSerializedBytes(int pver) {
103 | var buffer = new bytes.Buffer();
104 | bitcoinSerialize(buffer, pver);
105 | return buffer.asBytes();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/crypto/key_crypter_scrypt.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.crypto.key_crypter_scrypt;
2 |
3 | import "dart:math";
4 | import "dart:typed_data";
5 |
6 | import "package:pointycastle/api.dart";
7 | import "package:pointycastle/key_derivators/api.dart";
8 | import "package:pointycastle/key_derivators/scrypt.dart";
9 | import "package:pointycastle/padded_block_cipher/padded_block_cipher_impl.dart";
10 | import "package:pointycastle/paddings/pkcs7.dart";
11 | import "package:pointycastle/block/modes/cbc.dart";
12 | import "package:pointycastle/block/aes_fast.dart";
13 |
14 | import "package:bitcoin/core.dart";
15 | import "package:bitcoin/src/utils.dart" as utils;
16 |
17 | /**
18 | * Implements a KeyCrypter for BIP0038 using Scrypt.
19 | * There is also a separate constructor for when EC multiplication is used (and so ScryptParamters should be provided.)
20 | *
21 | * More info in BIP0038: https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
22 | */
23 | class KeyCrypterScrypt implements KeyCrypter {
24 | static const int SALT_LENGTH = 8;
25 |
26 | static const int BLOCK_LENGTH = 16;
27 |
28 | // (BIP0038 suggests using a 64-byte key and splitting that in two)
29 | static const int KEY_LENGTH = 32;
30 |
31 | // these are copied from BIP0038
32 | static const int SCRYPT_N = 16384;
33 | static const int SCRYPT_r = 8;
34 | static const int SCRYPT_p = 8;
35 |
36 | ScryptParameters _scryptParams;
37 |
38 | /**
39 | * Create a new KeyCrypter.
40 | *
41 | * If none are given, the BIP0038 defaults will be used.
42 | */
43 | KeyCrypterScrypt({Uint8List salt, int iterations}) {
44 | if (salt == null) salt = _randomBytes(SALT_LENGTH);
45 | if (iterations == null) iterations = SCRYPT_N;
46 | _scryptParams = generateScryptParams(salt, iterations);
47 | }
48 |
49 | KeyCrypterScrypt.withParams(ScryptParameters scryptParameters) {
50 | _scryptParams = scryptParameters;
51 | }
52 |
53 | //TODO temp, need true random bytes in salt
54 | static Uint8List _randomBytes(int n) {
55 | Random r = new Random();
56 | Uint8List result = new Uint8List(n);
57 | for (int i = 0; i < n; i++) result[i] = r.nextInt(256);
58 | return result;
59 | }
60 |
61 | /**
62 | * The salt must be of length [SALT_LENGTH];
63 | */
64 | static ScryptParameters generateScryptParams(Uint8List salt, [int iterations = SCRYPT_N]) {
65 | if (salt.length != SALT_LENGTH)
66 | throw new ArgumentError("Incorrect salt length: ${salt.length} instead of $SALT_LENGTH");
67 | return new ScryptParameters(iterations, SCRYPT_r, SCRYPT_p, KEY_LENGTH, salt);
68 | }
69 |
70 | KeyParameter deriveKey(String passphrase) {
71 | Uint8List passBytes = utils.utf8Encode(passphrase);
72 | Uint8List keyBytes = new Uint8List(_scryptParams.desiredKeyLength);
73 | try {
74 | Scrypt scrypt = new Scrypt()..init(_scryptParams);
75 | scrypt.deriveKey(passBytes, 0, keyBytes, 0);
76 | return new KeyParameter(keyBytes);
77 | } catch (e) {
78 | throw new KeyCrypterException("Could not derive key from passphrase and salt.", e);
79 | }
80 | }
81 |
82 | EncryptedPrivateKey encrypt(Uint8List privKey, KeyParameter aesKey) {
83 | if (privKey == null) throw new ArgumentError.notNull("privKey");
84 | if (aesKey == null) throw new ArgumentError.notNull("aesKey");
85 | if (privKey.isEmpty) throw new ArgumentError.value(privKey, "privKey", "must not be empty");
86 |
87 | if (privKey == null || aesKey == null) throw new ArgumentError();
88 | Uint8List iv = _randomBytes(BLOCK_LENGTH);
89 | BlockCipher cipher = _createBlockCipher(true, aesKey, iv);
90 | Uint8List encryptedKey = cipher.process(privKey);
91 | return new EncryptedPrivateKey(encryptedKey, iv);
92 | }
93 |
94 | Uint8List decrypt(EncryptedPrivateKey encryptedPrivKey, KeyParameter aesKey) {
95 | if (encryptedPrivKey == null) throw new ArgumentError.notNull("encryptedPrivKey");
96 | if (aesKey == null) throw new ArgumentError.notNull("aesKey");
97 |
98 | try {
99 | BlockCipher cipher = _createBlockCipher(false, aesKey, encryptedPrivKey.iv);
100 | return cipher.process(encryptedPrivKey.encryptedKey);
101 | } catch (e) {
102 | throw new KeyCrypterException("Could not decrypt key.", e);
103 | }
104 | }
105 |
106 | // create a new blockcipher used for encrypting and decryptingprivate keys
107 | BlockCipher _createBlockCipher(bool forEncryption, KeyParameter aesKey, Uint8List iv) {
108 | ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv);
109 | PaddedBlockCipher cipher =
110 | new PaddedBlockCipherImpl(new PKCS7Padding(), new CBCBlockCipher(new AESFastEngine()));
111 | cipher.init(forEncryption, new PaddedBlockCipherParameters(keyWithIv, null));
112 | return cipher;
113 | }
114 |
115 | @override
116 | String toString() => "Scrypt/AES";
117 |
118 | @override
119 | operator ==(dynamic other) {
120 | if (other.runtimeType != KeyCrypterScrypt) return false;
121 | if (identical(this, other)) return true;
122 | return _scryptParams == other._scryptParams;
123 | }
124 |
125 | @override
126 | int get hashCode => _scryptParams.hashCode;
127 | }
128 |
--------------------------------------------------------------------------------
/lib/src/wire/message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | typedef Message _MessageGenerator();
4 |
5 | abstract class Message extends BitcoinSerializable {
6 | static const String CMD_ADDR = "addr";
7 | static const String CMD_ALERT = "alert";
8 | static const String CMD_BLOCK = "block";
9 | static const String CMD_FILTERADD = "filteradd";
10 | static const String CMD_FILTERCLEAR = "filterclear";
11 | static const String CMD_FILTERLOAD = "filterload";
12 | static const String CMD_GETADDR = "getaddr";
13 | static const String CMD_GETBLOCKS = "getblocks";
14 | static const String CMD_GETDATA = "getdata";
15 | static const String CMD_GETHEADERS = "getheaders";
16 | static const String CMD_HEADERS = "headers";
17 | static const String CMD_INV = "inv";
18 | static const String CMD_MEMPOOL = "mempool";
19 | static const String CMD_MERKLEBLOCK = "merkleblock";
20 | static const String CMD_NOTFOUND = "notfound";
21 | static const String CMD_PING = "ping";
22 | static const String CMD_PONG = "pong";
23 | static const String CMD_TX = "tx";
24 | static const String CMD_VERACK = "verack";
25 | static const String CMD_VERSION = "version";
26 |
27 | static final Map _MESSAGE_GENERATORS = {
28 | CMD_ADDR: () => new AddressMessage.empty(),
29 | CMD_ALERT: () => new AlertMessage.empty(),
30 | CMD_BLOCK: () => new BlockMessage.empty(),
31 | CMD_FILTERADD: () => new FilterAddMessage.empty(),
32 | CMD_FILTERCLEAR: () => new FilterClearMessage.empty(),
33 | CMD_FILTERLOAD: () => new FilterLoadMessage.empty(),
34 | CMD_GETADDR: () => new GetAddressMessage.empty(),
35 | CMD_GETBLOCKS: () => new GetBlocksMessage.empty(),
36 | CMD_GETDATA: () => new GetDataMessage.empty(),
37 | CMD_GETHEADERS: () => new GetHeadersMessage.empty(),
38 | CMD_HEADERS: () => new HeadersMessage.empty(),
39 | CMD_INV: () => new InventoryMessage.empty(),
40 | CMD_MEMPOOL: () => new MemPoolMessage.empty(),
41 | CMD_MERKLEBLOCK: () => new MerkleBlockMessage.empty(),
42 | CMD_NOTFOUND: () => new NotFoundMessage.empty(),
43 | CMD_PING: () => new PingMessage.empty(),
44 | CMD_PONG: () => new PongMessage.empty(),
45 | CMD_TX: () => new TransactionMessage.empty(),
46 | CMD_VERACK: () => new VerackMessage.empty(),
47 | CMD_VERSION: () => new VersionMessage.empty(),
48 | };
49 |
50 | static const int HEADER_LENGTH = 24; // = 4 + COMMAND_LENGTH + 4 + 4;
51 | static const int COMMAND_LENGTH = 12;
52 |
53 | String get command;
54 |
55 | Message();
56 |
57 | factory Message.forCommand(String command) {
58 | if (!_MESSAGE_GENERATORS.containsKey(command)) {
59 | throw new ArgumentError("$command is not a valid message command");
60 | }
61 | return _MESSAGE_GENERATORS[command]();
62 | }
63 |
64 | void bitcoinDeserialize(bytes.Reader reader, int pver);
65 | void bitcoinSerialize(bytes.Buffer buffer, int pver);
66 |
67 | /// Decode a serialized message.
68 | static Message decode(Uint8List msgBytes, int magicValue, int pver) {
69 | if (msgBytes.length < HEADER_LENGTH)
70 | throw new SerializationException("Too few bytes to be a Message");
71 |
72 | // create a Reader for deserializing
73 | var reader = new bytes.Reader(msgBytes);
74 |
75 | // verify the magic value
76 | int magic = readUintLE(reader);
77 | if (magic != magicValue) {
78 | throw new SerializationException("Invalid magic value: $magic. Expected $magicValue.");
79 | }
80 |
81 | // read the command, length and checksum
82 | String cmd = _readCommand(readBytes(reader, COMMAND_LENGTH));
83 | int payloadLength = readUintLE(reader);
84 | Uint8List checksum = readBytes(reader, 4);
85 |
86 | // create a checksum reader to be able to determine the checksum afterwards
87 | ChecksumReader payloadReader = new ChecksumReader(reader, new crypto.DoubleSHA256Digest());
88 | int preLength = reader.remainingLength;
89 |
90 | // generate an empty concrete message instance and make it parse
91 | Message msg = new Message.forCommand(cmd);
92 | msg.bitcoinDeserialize(payloadReader, pver);
93 | int postLength = reader.remainingLength;
94 |
95 | // check if the payload was of the claimed size
96 | if (preLength - postLength != payloadLength) {
97 | throw new SerializationException("Incorrect payload length in message header "
98 | "(actual: ${(preLength - postLength)}, expected: $payloadLength");
99 | }
100 |
101 | // check the checksum
102 | Uint8List actualChecksum = payloadReader.checksum().sublist(0, 4);
103 | if (!utils.equalLists(checksum, actualChecksum)) {
104 | throw new SerializationException("Incorrect checksum provided in serialized message "
105 | "(actual: ${CryptoUtils.bytesToHex(actualChecksum)}, "
106 | "expected: ${CryptoUtils.bytesToHex(checksum)})");
107 | }
108 |
109 | return msg;
110 | }
111 |
112 | /// Encode a message to serialized format.
113 | static Uint8List encode(Message msg, int magicValue, int pver) {
114 | var buffer = new bytes.Buffer();
115 |
116 | // write magic value and command
117 | writeUintLE(buffer, magicValue);
118 | buffer.add(_encodeCommand(msg.command));
119 |
120 | // serialize the payload
121 | ChecksumBuffer payloadBuffer = new ChecksumBuffer(new crypto.DoubleSHA256Digest());
122 | msg.bitcoinSerialize(payloadBuffer, pver);
123 |
124 | // write payload size and checksum
125 | writeUintLE(buffer, payloadBuffer.length);
126 | buffer.add(payloadBuffer.checksum().sublist(0, 4));
127 |
128 | // write the actual payload
129 | writeBytes(buffer, payloadBuffer.asBytes());
130 |
131 | return buffer.asBytes();
132 | }
133 |
134 | static String _readCommand(Uint8List bytes) {
135 | int word = COMMAND_LENGTH;
136 | while (bytes[word - 1] == 0) word--;
137 | return ASCII.decode(bytes.sublist(0, word));
138 | }
139 |
140 | static List _encodeCommand(String command) {
141 | List commandBytes = new List.from(ASCII.encode(command));
142 | while (commandBytes.length < COMMAND_LENGTH) commandBytes.add(0);
143 | return commandBytes;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/test/crypto/key_crypter_scrypt_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.crypto.key_crypter_scrypt;
2 |
3 | import "dart:typed_data";
4 | import "dart:math";
5 |
6 | import "package:cryptoutils/cryptoutils.dart";
7 | import "package:pointycastle/key_derivators/api.dart";
8 | import "package:uuid/uuid.dart";
9 |
10 | import "package:test/test.dart";
11 |
12 | import "package:bitcoin/core.dart";
13 | import "package:bitcoin/src/utils.dart" as utils;
14 | import "package:bitcoin/crypto/key_crypter_scrypt.dart";
15 |
16 | // Nonsense bytes for encryption test.
17 | final Uint8List _TEST_BYTES1 = new Uint8List.fromList([
18 | 0,
19 | -101,
20 | 2,
21 | 103,
22 | -4,
23 | 105,
24 | 6,
25 | 107,
26 | 8,
27 | -109,
28 | 10,
29 | 111,
30 | -12,
31 | 113,
32 | 14,
33 | -115,
34 | 16,
35 | 117,
36 | -18,
37 | 119,
38 | 20,
39 | 121,
40 | 22,
41 | 123,
42 | -24,
43 | 125,
44 | 26,
45 | 127,
46 | -28,
47 | 29,
48 | -30,
49 | 31
50 | ]);
51 |
52 | final String _PASSWORD1 = "aTestPassword";
53 | final String _PASSWORD2 = "0123456789";
54 |
55 | final String _WRONG_PASSWORD = "thisIsTheWrongPassword";
56 |
57 | ScryptParameters _scryptParameters;
58 |
59 | void main() {
60 | group("crypto.KeyCrypterScrypt", () {
61 | setUp(() {
62 | Uint8List salt = new Uint8List(KeyCrypterScrypt.SALT_LENGTH);
63 | Random r = new Random();
64 | for (int i = 0; i < KeyCrypterScrypt.SALT_LENGTH; i++) salt[i] = r.nextInt(256);
65 | _scryptParameters = KeyCrypterScrypt.generateScryptParams(salt, 4);
66 | });
67 |
68 | test("KeyCrypterGood1", () {
69 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt.withParams(_scryptParameters);
70 |
71 | // Encrypt.
72 | EncryptedPrivateKey encryptedPrivateKey =
73 | keyCrypter.encrypt(_TEST_BYTES1, keyCrypter.deriveKey(_PASSWORD1));
74 | expect(encryptedPrivateKey, isNotNull);
75 |
76 | // Decrypt.
77 | Uint8List reborn = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(_PASSWORD1));
78 | print("Original: " + CryptoUtils.bytesToHex(_TEST_BYTES1));
79 | print("Reborn : " + CryptoUtils.bytesToHex(reborn));
80 | expect(CryptoUtils.bytesToHex(reborn), equals(CryptoUtils.bytesToHex(_TEST_BYTES1)));
81 | });
82 |
83 | test("KeyCrypterGood2", () {
84 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt.withParams(_scryptParameters);
85 |
86 | print("EncrypterDecrypterTest: Trying random UUIDs for plainText and passwords :");
87 | int numberOfTests = 16;
88 | Uuid uuid = new Uuid();
89 | for (int i = 0; i < numberOfTests; i++) {
90 | // Create a UUID as the plaintext and use another for the password.
91 | String plainText = uuid.v4();
92 | String password = uuid.v4();
93 |
94 | EncryptedPrivateKey encryptedPrivateKey =
95 | keyCrypter.encrypt(utils.utf8Encode(plainText), keyCrypter.deriveKey(password));
96 |
97 | expect(encryptedPrivateKey, isNotNull);
98 |
99 | Uint8List reconstructedPlainBytes =
100 | keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(password));
101 | expect(CryptoUtils.bytesToHex(reconstructedPlainBytes),
102 | equals(CryptoUtils.bytesToHex(utils.utf8Encode(plainText))));
103 | print('.');
104 | }
105 | print(" Done.");
106 | });
107 |
108 | test("KeyCrypterWrongPasswd", () {
109 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt.withParams(_scryptParameters);
110 |
111 | // create a longer encryption string
112 | StringBuffer stringBuffer = new StringBuffer();
113 | for (int i = 0; i < 100; i++) {
114 | stringBuffer..write(i)..write(" ")..write("The quick brown fox");
115 | }
116 |
117 | EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(
118 | utils.utf8Encode(stringBuffer.toString()), keyCrypter.deriveKey(_PASSWORD2));
119 | expect(encryptedPrivateKey, isNotNull);
120 |
121 | expect(() => keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(_WRONG_PASSWORD)),
122 | throwsA(anything));
123 | });
124 |
125 | test("EncryptDecryptBytes1", () {
126 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt.withParams(_scryptParameters);
127 |
128 | // Encrypt bytes.
129 | EncryptedPrivateKey encryptedPrivateKey =
130 | keyCrypter.encrypt(_TEST_BYTES1, keyCrypter.deriveKey(_PASSWORD1));
131 | expect(encryptedPrivateKey, isNotNull);
132 | print(
133 | "\nEncrypterDecrypterTest: cipherBytes = \nlength = ${encryptedPrivateKey.encryptedKey.length}\n---------------\n${CryptoUtils.bytesToHex(encryptedPrivateKey.encryptedKey)}\n---------------\n");
134 |
135 | Uint8List rebornPlainBytes =
136 | keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(_PASSWORD1));
137 |
138 | print("Original: " + CryptoUtils.bytesToHex(_TEST_BYTES1));
139 | print("Reborn1 : " + CryptoUtils.bytesToHex(rebornPlainBytes));
140 | expect(
141 | CryptoUtils.bytesToHex(rebornPlainBytes), equals(CryptoUtils.bytesToHex(_TEST_BYTES1)));
142 | });
143 |
144 | test("EncryptDecryptBytes2", () {
145 | KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt.withParams(_scryptParameters);
146 |
147 | // Encrypt random bytes of various lengths up to length 50.
148 | Random r = new Random();
149 | for (int i = 1; i < 50; i++) {
150 | Uint8List plainBytes = new Uint8List(i);
151 | for (int j = 0; j < i; j++) plainBytes[j] = r.nextInt(256);
152 | EncryptedPrivateKey encryptedPrivateKey =
153 | keyCrypter.encrypt(plainBytes, keyCrypter.deriveKey(_PASSWORD1));
154 | expect(encryptedPrivateKey, isNotNull);
155 |
156 | Uint8List rebornPlainBytes =
157 | keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(_PASSWORD1));
158 |
159 | print("Original: ($i) " + CryptoUtils.bytesToHex(plainBytes));
160 | print("Reborn1 : ($i) " + CryptoUtils.bytesToHex(rebornPlainBytes));
161 | expect(
162 | CryptoUtils.bytesToHex(rebornPlainBytes), equals(CryptoUtils.bytesToHex(plainBytes)));
163 | }
164 | });
165 | });
166 | }
167 |
--------------------------------------------------------------------------------
/test/wire/message_serialization_test.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.test.wire.serialization;
2 |
3 | import "dart:typed_data";
4 |
5 | import "package:cryptoutils/cryptoutils.dart";
6 |
7 | import "package:test/test.dart";
8 |
9 | import "package:bitcoin/core.dart";
10 | import "package:bitcoin/wire.dart";
11 |
12 | final Uint8List _addrMessage = CryptoUtils.hexToBytes("f9beb4d96164647200000000000000001f000000" +
13 | "ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d");
14 |
15 | final Uint8List _txMessage = CryptoUtils.hexToBytes(
16 | "F9 BE B4 D9 74 78 00 00 00 00 00 00 00 00 00 00" +
17 | "02 01 00 00 E2 93 CD BE 01 00 00 00 01 6D BD DB" +
18 | "08 5B 1D 8A F7 51 84 F0 BC 01 FA D5 8D 12 66 E9" +
19 | "B6 3B 50 88 19 90 E4 B4 0D 6A EE 36 29 00 00 00" +
20 | "00 8B 48 30 45 02 21 00 F3 58 1E 19 72 AE 8A C7" +
21 | "C7 36 7A 7A 25 3B C1 13 52 23 AD B9 A4 68 BB 3A" +
22 | "59 23 3F 45 BC 57 83 80 02 20 59 AF 01 CA 17 D0" +
23 | "0E 41 83 7A 1D 58 E9 7A A3 1B AE 58 4E DE C2 8D" +
24 | "35 BD 96 92 36 90 91 3B AE 9A 01 41 04 9C 02 BF" +
25 | "C9 7E F2 36 CE 6D 8F E5 D9 40 13 C7 21 E9 15 98" +
26 | "2A CD 2B 12 B6 5D 9B 7D 59 E2 0A 84 20 05 F8 FC" +
27 | "4E 02 53 2E 87 3D 37 B9 6F 09 D6 D4 51 1A DA 8F" +
28 | "14 04 2F 46 61 4A 4C 70 C0 F1 4B EF F5 FF FF FF" +
29 | "FF 02 40 4B 4C 00 00 00 00 00 19 76 A9 14 1A A0" +
30 | "CD 1C BE A6 E7 45 8A 7A BA D5 12 A9 D9 EA 1A FB" +
31 | "22 5E 88 AC 80 FA E9 C7 00 00 00 00 19 76 A9 14" +
32 | "0E AB 5B EA 43 6A 04 84 CF AB 12 48 5E FD A0 B7" +
33 | "8B 4E CC 52 88 AC 00 00 00 00");
34 |
35 | final Uint8List _txBytes = _txMessage.sublist(Message.HEADER_LENGTH);
36 |
37 | //final NetworkParameters _mainParams = NetworkParameters.MAIN_NET;
38 | final int magic = NetworkParameters.MAIN_NET.magicValue;
39 |
40 | final int pver = 70001;
41 |
42 | void main() {
43 | group("wire.MessageSerialization", () {
44 | test("addr", () {
45 | // the actual data from https://en.bitcoin.it/wiki/Protocol_specification#addr
46 | AddressMessage a = Message.decode(_addrMessage, magic, pver);
47 | expect(a.addresses.length, equals(1));
48 | PeerAddress pa = a.addresses[0];
49 | expect(pa.port, equals(8333));
50 | expect(pa.address.sublist(12, 16), equals(Uri.parseIPv4Address("10.0.0.1")));
51 | }); //
52 |
53 | test("txVsTxmDeseri", () {
54 | TransactionMessage txm = Message.decode(_txMessage, magic, pver);
55 | Transaction tx = txm.transaction;
56 | Transaction tx2 = new Transaction.fromBitcoinSerialization(_txBytes, pver);
57 | expect(tx2, equals(tx));
58 | });
59 |
60 | test("headers1", () {
61 | HeadersMessage hm = Message.decode(
62 | CryptoUtils.hexToBytes("f9beb4d9686561" +
63 | "646572730000000000520000005d4fab8101010000006fe28c0ab6f1b372c1a6a246ae6" +
64 | "3f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677b" +
65 | "a1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e3629900"),
66 | magic,
67 | pver);
68 |
69 | // The first block after the genesis
70 | // http://blockexplorer.com/b/1
71 | Block block = hm.headers[0];
72 | String hash = block.hash.toString();
73 | expect(hash, equals("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"));
74 |
75 | expect(block.transactions, isNull);
76 |
77 | expect(block.merkleRoot.toHex(),
78 | equals("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"));
79 | });
80 |
81 | test("headers2", () {
82 | HeadersMessage hm = Message.decode(
83 | CryptoUtils.hexToBytes("f9beb4d96865616465"
84 | "72730000000000e701000085acd4ea06010000006fe28c0ab6f1b372c1a6a246ae63f74f931e"
85 | "8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1c"
86 | "db606e857233e0e61bc6649ffff001d01e3629900010000004860eb18bf1b1620e37e9490fc8a"
87 | "427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36"
88 | "ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610001000000bddd99ccfda39da1b108ce1"
89 | "a5d70038d0a967bacb68b6b63065f626a0000000044f672226090d85db9a9f2fbfe5f0f9609b387"
90 | "af7be5b7fbb7a1767c831c9e995dbe6649ffff001d05e0ed6d00010000004944469562ae1c2c74"
91 | "d9a535e00b6f3e40ffbad4f2fda3895501b582000000007a06ea98cd40ba2e3288262b28638cec"
92 | "5337c1456aaf5eedc8e9e5a20f062bdf8cc16649ffff001d2bfee0a9000100000085144a84488e"
93 | "a88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023"
94 | "370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e4770001000000fc33f5"
95 | "96f822a0a1951ffdbf2a897b095636ad871707bf5d3162729b00000000379dfb96a5ea8c81700ea4"
96 | "ac6b97ae9a9312b2d4301a29580e924ee6761a2520adc46649ffff001d189c4c9700"),
97 | magic,
98 | pver);
99 |
100 | int nBlocks = hm.headers.length;
101 | expect(nBlocks, equals(6));
102 |
103 | // index 0 block is the number 1 block in the block chain
104 | // http://blockexplorer.com/b/1
105 | Block zeroBlock = hm.headers[0];
106 | String zeroBlockHash = zeroBlock.hash.toString();
107 |
108 | expect(zeroBlockHash,
109 | equals("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"));
110 | expect(zeroBlock.nonce, equals(2573394689));
111 |
112 | Block thirdBlock = hm.headers[3];
113 | String thirdBlockHash = thirdBlock.hash.toString();
114 |
115 | // index 3 block is the number 4 block in the block chain
116 | // http://blockexplorer.com/b/4
117 | expect(thirdBlockHash,
118 | equals("000000004ebadb55ee9096c9a2f8880e09da59c0d68b1c228da88e48844a1485"));
119 | expect(thirdBlock.nonce, equals(2850094635));
120 | });
121 |
122 | test("packetHeader", () {
123 | expect(() => Message.decode(new Uint8List(0), magic, pver),
124 | throwsA(new isInstanceOf()));
125 |
126 | // Message with a Message size which is 1 too big, in little endian format.
127 | Uint8List wrongMessageLength =
128 | CryptoUtils.hexToBytes("000000000000000000000000010000020000000000");
129 | expect(() => Message.decode(wrongMessageLength, magic, pver),
130 | throwsA(new isInstanceOf()));
131 | });
132 | });
133 | }
134 |
--------------------------------------------------------------------------------
/lib/src/core/partial_merkle_tree.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | /**
4 | * A data structure that contains proofs of block inclusion for one or more transactions, in an efficient manner.
5 | *
6 | * The encoding works as follows: we traverse the tree in depth-first order, storing a bit for each traversed node,
7 | * signifying whether the node is the parent of at least one matched leaf txid (or a matched txid itself). In case we
8 | * are at the leaf level, or this bit is 0, its merkle node hash is stored, and its children are not explored further.
9 | * Otherwise, no hash is stored, but we recurse into both (or the only) child branch. During decoding, the same
10 | * depth-first traversal is performed, consuming bits and hashes as they were written during encoding.
11 | *
12 | * The serialization is fixed and provides a hard guarantee about the encoded size,
13 | * SIZE <= 10 + ceil(32.25*N) where N represents the number of leaf nodes of the partial tree. N itself
14 | * is bounded by:
15 | *
16 | *
17 | * N <= total_transactions
18 | * N <= 1 + matched_transactions*tree_height
19 | *
20 | *
21 | * The serialization format:
22 | * - uint32 total_transactions (4 bytes)
23 | * - varint number of hashes (1-3 bytes)
24 | * - uint256[] hashes in depth-first order (<= 32*N bytes)
25 | * - varint number of bytes of flag bits (1-3 bytes)
26 | * - Uint8List flag bits, packed per 8 in a byte, least significant bit first (<= 2*N-1 bits)
27 | * The size constraints follow from this.
28 | */
29 | class PartialMerkleTree extends BitcoinSerializable {
30 | // the total number of transactions in the block
31 | int transactionCount;
32 |
33 | // node-is-parent-of-matched-txid bits
34 | Uint8List matchedChildBits;
35 |
36 | // txids and internal hashes
37 | List hashes;
38 |
39 | PartialMerkleTree(
40 | {int this.transactionCount, Uint8List this.matchedChildBits, List this.hashes});
41 |
42 | /// Create an empty instance.
43 | PartialMerkleTree.empty();
44 |
45 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
46 | writeUintLE(buffer, transactionCount);
47 | writeVarInt(buffer, hashes.length);
48 | for (Hash256 hash in hashes) writeSHA256(buffer, hash);
49 | writeByteArray(buffer, matchedChildBits);
50 | }
51 |
52 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
53 | transactionCount = readUintLE(reader);
54 | int nbHashes = readVarInt(reader);
55 | hashes = new List(nbHashes);
56 | for (int i = 0; i < nbHashes; i++) {
57 | hashes[i] = readSHA256(reader);
58 | }
59 | matchedChildBits = readByteArray(reader);
60 | }
61 |
62 | /**
63 | * Extracts tx hashes that are in this merkle tree
64 | * and returns the merkle root of this tree.
65 | *
66 | * The returned root should be checked against the
67 | * merkle root contained in the block header for security.
68 | *
69 | * @param matchedHashes A list which will contain the matched txn (will be cleared)
70 | * Required to be a LinkedHashSet in order to retain order or transactions in the block
71 | * @return the merkle root of this merkle tree
72 | */
73 | Hash256 getTxnHashAndMerkleRoot(List matchedHashes) {
74 | matchedHashes.clear();
75 |
76 | // An empty set will not work
77 | if (transactionCount == 0)
78 | throw new VerificationException("Got a CPartialMerkleTree with 0 transactions");
79 | // check for excessively high numbers of transactions
80 | if (transactionCount >
81 | Block.MAX_BLOCK_SIZE ~/
82 | 60) // 60 is the lower bound for the size of a serialized CTransaction
83 | throw new VerificationException(
84 | "Got a CPartialMerkleTree with more transactions than is possible");
85 | // there can never be more hashes provided than one for every txid
86 | if (hashes.length > transactionCount)
87 | throw new VerificationException(
88 | "Got a CPartialMerkleTree with more hashes than transactions");
89 | // there must be at least one bit per node in the partial tree, and at least one node per hash
90 | if (matchedChildBits.length * 8 < hashes.length)
91 | throw new VerificationException(
92 | "Got a CPartialMerkleTree with fewer matched bits than hashes");
93 | // calculate height of tree
94 | int height = 0;
95 | while (_getTreeWidth(height, transactionCount) > 1) height++;
96 | // traverse the partial tree
97 | _ValuesUsedForPMT used = new _ValuesUsedForPMT();
98 | Hash256 merkleRoot = new Hash256(_recursiveExtractHashes(height, 0, used, matchedHashes));
99 | // verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
100 | if ((used.bitsUsed + 7) ~/ 8 != matchedChildBits.length ||
101 | // verify that all hashes were consumed
102 | used.hashesUsed != hashes.length)
103 | throw new VerificationException(
104 | "Got a CPartialMerkleTree that didn't need all the data it provided");
105 |
106 | return merkleRoot;
107 | }
108 |
109 | // helper function to efficiently calculate the number of nodes at given height in the merkle tree
110 | static int _getTreeWidth(int height, int txCount) => (txCount + (1 << height) - 1) >> height;
111 |
112 | // recursive function that traverses tree nodes, consuming the bits and hashes produced by TraverseAndBuild.
113 | // it returns the hash of the respective node.
114 | Uint8List _recursiveExtractHashes(
115 | int height, int pos, _ValuesUsedForPMT used, List matchedHashes) {
116 | if (used.bitsUsed >= matchedChildBits.length * 8) {
117 | // overflowed the bits array - failure
118 | throw new VerificationException("CPartialMerkleTree overflowed its bits array");
119 | }
120 | bool parentOfMatch = utils.checkBitLE(matchedChildBits, used.bitsUsed++);
121 | if (height == 0 || !parentOfMatch) {
122 | // if at height 0, or nothing interesting below, use stored hash and do not descend
123 | if (used.hashesUsed >= hashes.length) {
124 | // overflowed the hash array - failure
125 | throw new VerificationException("CPartialMerkleTree overflowed its hash array");
126 | }
127 | if (height == 0 && parentOfMatch) // in case of height 0, we have a matched txid
128 | matchedHashes.add(hashes[used.hashesUsed]);
129 | return hashes[used.hashesUsed++].asBytes();
130 | } else {
131 | // otherwise, descend into the subtrees to extract matched txids and hashes
132 | Uint8List left = _recursiveExtractHashes(height - 1, pos * 2, used, matchedHashes), right;
133 | if (pos * 2 + 1 < _getTreeWidth(height - 1, transactionCount))
134 | right = _recursiveExtractHashes(height - 1, pos * 2 + 1, used, matchedHashes);
135 | else
136 | right = left;
137 | // and combine them before returning
138 | return utils.reverseBytes(
139 | crypto.doubleDigestTwoInputs(utils.reverseBytes(left), utils.reverseBytes(right)));
140 | }
141 | }
142 | }
143 |
144 | class _ValuesUsedForPMT {
145 | int bitsUsed, hashesUsed;
146 | _ValuesUsedForPMT([this.bitsUsed = 0, this.hashesUsed = 0]);
147 | }
148 |
--------------------------------------------------------------------------------
/lib/src/wire/messages/version_message.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.wire;
2 |
3 | class VersionMessage extends Message {
4 | @override
5 | String get command => Message.CMD_VERSION;
6 |
7 | static final BigInteger SERVICE_NODE_NETWORK = BigInteger.ONE;
8 |
9 | int clientVersion;
10 | BigInteger services;
11 | int time;
12 | PeerAddress myAddress;
13 | PeerAddress theirAddress;
14 | int nonce;
15 | String subVer;
16 | int lastHeight;
17 | bool relayBeforeFilter;
18 |
19 | /** The version of this library release, as a string. */
20 | static final String DARTCOIN_VERSION = "0.0.0-alpha";
21 |
22 | /** The value that is prepended to the subVer field of this application. */
23 | static final String LIBRARY_SUBVER = "/Dartcoin:" + DARTCOIN_VERSION + "/";
24 |
25 | /**
26 | * Create a new VersionMessage.
27 | *
28 | * Most parameters can be left blank, the most important ones are
29 | * [lastHeight] and [relayBeforeFilter], all others most probably have the default value.
30 | */
31 | VersionMessage(
32 | {int this.lastHeight: 0,
33 | bool this.relayBeforeFilter: false,
34 | int this.clientVersion: NetworkParameters.PROTOCOL_VERSION,
35 | BigInteger this.services,
36 | int this.time: 0,
37 | PeerAddress this.myAddress,
38 | PeerAddress this.theirAddress,
39 | int this.nonce,
40 | String this.subVer}) {
41 | services = services ?? BigInteger.ZERO;
42 | nonce = nonce ?? new Random().nextInt(0xffffffff);
43 | subVer = subVer ?? LIBRARY_SUBVER;
44 | // make sure a PeerAddress instance with protocolVersion = 0 is used
45 | myAddress = myAddress ?? new PeerAddress.localhost(services: services);
46 | theirAddress =
47 | theirAddress ?? new PeerAddress.localhost(services: services);
48 | }
49 |
50 | /// Create an empty instance.
51 | VersionMessage.empty();
52 |
53 | /**
54 | * Appends the given user-agent information to the subVer field. The subVer is composed of a series of
55 | * name:version pairs separated by slashes in the form of a path. For example a typical subVer field for BitCoinJ
56 | * users might look like "/BitCoinJ:0.4-SNAPSHOT/MultiBit:1.2/" where libraries come further to the left.
57 | *
58 | * There can be as many components as you feel a need for, and the version string can be anything, but it is
59 | * recommended to use A.B.C where A = major, B = minor and C = revision for software releases, and dates for
60 | * auto-generated source repository snapshots. A valid subVer begins and ends with a slash, therefore name
61 | * and version are not allowed to contain such characters.
62 | *
63 | * Anything put in the "comments" field will appear in brackets and may be used for platform info, or anything
64 | * else. For example, calling appendToSubVer("MultiBit", "1.0", "Windows") will result in a subVer being
65 | * set of "/BitCoinJ:1.0/MultiBit:1.0(Windows)/. Therefore the / ( and ) characters are reserved in all these
66 | * components. If you don't want to add a comment (recommended), pass null.
67 | *
68 | * See BIP 14 for more information.
69 | *
70 | * @param comments Optional (can be null) platform or other node specific information.
71 | * @throws IllegalArgumentException if name, version or comments contains invalid characters.
72 | */
73 | void appendToSubVer(String name, String version, [String comments]) {
74 | if (!isValidSubVerComponent(name) ||
75 | !isValidSubVerComponent(version) ||
76 | (comments != null && !isValidSubVerComponent(comments)))
77 | throw new Exception("Invalid format");
78 | if (comments != null)
79 | subVer += "$name:$version($comments)/";
80 | else
81 | subVer += "$name:$version/";
82 | }
83 |
84 | static bool isValidSubVerComponent(String component) =>
85 | !(component.contains("/") ||
86 | component.contains("(") ||
87 | component.contains(")"));
88 |
89 | /**
90 | * Returns true if the clientVersion field is >= Pong.MIN_PROTOCOL_VERSION. If it is then ping() is usable.
91 | */
92 | bool get isPingPongSupported =>
93 | clientVersion >= PongMessage.MIN_PROTOCOL_VERSION;
94 |
95 | /**
96 | * Returns true if the clientVersion field is >= FilteredBlock.MIN_PROTOCOL_VERSION. If it is then Bloom filtering
97 | * is available and the memory pool of the remote peer will be queried when the downloadData property is true.
98 | */
99 | bool get isBloomFilteringSupported =>
100 | clientVersion >= FilteredBlock.MIN_PROTOCOL_VERSION;
101 |
102 | /**
103 | * Returns true if the version message indicates the sender has a full copy of the block chain,
104 | * or if it's running in client mode (only has the headers).
105 | */
106 | bool get hasBlockChain =>
107 | (services & SERVICE_NODE_NETWORK) == SERVICE_NODE_NETWORK;
108 |
109 | @override
110 | bool operator ==(dynamic other) {
111 | if (other.runtimeType != VersionMessage) return false;
112 | return other.lastHeight == lastHeight &&
113 | other.clientVersion == clientVersion &&
114 | other.services == services &&
115 | other.time == time &&
116 | other.subVer == subVer &&
117 | other.myAddress == myAddress &&
118 | other.theirAddress == theirAddress &&
119 | other.relayBeforeFilter == relayBeforeFilter;
120 | }
121 |
122 | @override
123 | int get hashCode {
124 | return lastHeight ^
125 | clientVersion ^
126 | services.hashCode ^
127 | time ^
128 | subVer.hashCode ^
129 | myAddress.hashCode ^
130 | theirAddress.hashCode * (relayBeforeFilter ? 1 : 2);
131 | }
132 |
133 | @override
134 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
135 | clientVersion = readUintLE(reader);
136 | services = utils.bytesToUBigIntLE(readBytes(reader, 8)); //_readUintLE(8);
137 | time = readUintLE(reader, 8);
138 | // for PeerAddresses in the version message, the protocolVersion must be hard coded to 0
139 | myAddress = readObject(reader, new PeerAddress.empty(), 0);
140 | if (clientVersion < 106) return;
141 | // only when protocolVersion >= 106
142 | theirAddress = readObject(reader, new PeerAddress.empty(), 0);
143 | nonce = readUintLE(reader, 8);
144 | // initialize default values for flags that may be missing from old nodes
145 | subVer = "";
146 | lastHeight = 0;
147 | relayBeforeFilter = true;
148 | if (reader.remainingLength > 0) {
149 | subVer = readVarStr(reader);
150 | }
151 | if (reader.remainingLength > 0) {
152 | lastHeight = readUintLE(reader);
153 | }
154 | if (reader.remainingLength > 0) {
155 | relayBeforeFilter = readUintLE(reader, 1) != 0;
156 | }
157 | }
158 |
159 | @override
160 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
161 | writeUintLE(buffer, clientVersion);
162 | writeBytes(buffer, utils.uBigIntToBytesLE(services, 8));
163 | writeUintLE(buffer, time, 8);
164 | writeObject(buffer, myAddress, 0);
165 | // we are version >= 106
166 | writeObject(buffer, theirAddress, 0);
167 | writeUintLE(buffer, nonce, 8);
168 | writeVarStr(buffer, subVer);
169 | writeUintLE(buffer, lastHeight);
170 | writeBytes(buffer, [relayBeforeFilter ? 1 : 0]);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/lib/crypto/mnemonic_code.dart:
--------------------------------------------------------------------------------
1 | library bitcoin.crypto.mnemonic_code;
2 |
3 | import "dart:convert" show UTF8;
4 | import "dart:typed_data";
5 |
6 | import "package:cryptoutils/cryptoutils.dart";
7 | import "package:pointycastle/api.dart";
8 | import "package:pointycastle/digests/sha256.dart";
9 | import "package:pointycastle/digests/sha512.dart";
10 | import "package:pointycastle/key_derivators/api.dart";
11 | import "package:pointycastle/key_derivators/pbkdf2.dart";
12 | import "package:pointycastle/macs/hmac.dart";
13 |
14 | import "package:bitcoin/src/crypto.dart" as crypto;
15 | import "package:bitcoin/src/utils.dart" as utils;
16 |
17 | /**
18 | * A MnemonicCode object may be used to convert between binary seed values and
19 | * lists of words per the BIP 39 specification.
20 | *
21 | * For more info: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
22 | */
23 | class MnemonicCode {
24 | static const String BIP39_ENGLISH_SHA256 =
25 | "ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db";
26 |
27 | static const int _PBKDF2_ROUNDS = 2048;
28 |
29 | List _wordList;
30 |
31 | /**
32 | * Creates a [MnemonicCode] object using the [wordList] word list.
33 | *
34 | * If [wordListDigest] is not [null], the SHA-256 digest of the [wordList] will be checked.
35 | * If [wordListDigest] is omitted, [BIP39_ENGLISH_SHA256] will be used.
36 | * So [null] should be passed explicitly to avoid the digest check.
37 | */
38 | MnemonicCode(Iterable wordList, [String wordListDigest = BIP39_ENGLISH_SHA256]) {
39 | if (wordList.length != 2048)
40 | throw new ArgumentError("The word list does not contain exactly 2048 words.");
41 |
42 | _wordList = new List();
43 | SHA256Digest md = new SHA256Digest();
44 | for (String word in wordList) {
45 | List wordBytes = UTF8.encode(word);
46 | md.update(wordBytes, 0, wordBytes.length);
47 | _wordList.add(word);
48 | }
49 | if (wordListDigest != null) {
50 | Uint8List digest = new Uint8List(md.digestSize);
51 | md.doFinal(digest, 0);
52 | if (!utils.equalLists(digest, CryptoUtils.hexToBytes(wordListDigest)))
53 | throw new ArgumentError("Invalid wordlist digest");
54 | }
55 | }
56 |
57 | /**
58 | * Convert mnemonic word list to seed.
59 | */
60 | static Uint8List toSeed(List words, String passphrase) {
61 | // To create binary seed from mnemonic, we use PBKDF2 function
62 | // with mnemonic sentence (in UTF-8) used as a password and
63 | // string "mnemonic" + passphrase (again in UTF-8) used as a
64 | // salt. Iteration count is set to 2048 and HMAC-SHA512 is
65 | // used as a pseudo-random function. Desired length of the
66 | // derived key is 512 bits (= 64 bytes).
67 | //
68 | Uint8List pass = utils.utf8Encode(words.join(" "));
69 | Uint8List salt = utils.utf8Encode("mnemonic" + passphrase);
70 |
71 | KeyDerivator deriv = new PBKDF2KeyDerivator(new HMac(new SHA512Digest(), 128));
72 | deriv.init(new Pbkdf2Parameters(salt, _PBKDF2_ROUNDS, 64));
73 |
74 | return deriv.process(pass);
75 | }
76 |
77 | /**
78 | * Convert mnemonic word list to original entropy value.
79 | */
80 | Uint8List toEntropy(List words) {
81 | if (words.length % 3 > 0)
82 | throw new MnemonicException("Word list size must be multiple of three words.");
83 |
84 | // Look up all the words in the list and construct the
85 | // concatenation of the original entropy and the checksum.
86 | //
87 | int concatLenBits = words.length * 11;
88 | List concatBits = new List(concatLenBits);
89 | int wordindex = 0;
90 | for (String word in words) {
91 | // Find the words index in the wordlist.
92 |
93 | int ndx = utils.binarySearch(_wordList, word);
94 | if (ndx < 0) throw new MnemonicException.word(word);
95 |
96 | // Set the next 11 bits to the value of the index.
97 | for (int ii = 0; ii < 11; ++ii)
98 | concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0;
99 | ++wordindex;
100 | }
101 |
102 | int checksumLengthBits = concatLenBits ~/ 33;
103 | int entropyLengthBits = concatLenBits - checksumLengthBits;
104 |
105 | // Extract original entropy as bytes.
106 | Uint8List entropy = new Uint8List(entropyLengthBits ~/ 8);
107 | for (int ii = 0; ii < entropy.length; ++ii)
108 | for (int jj = 0; jj < 8; ++jj) if (concatBits[(ii * 8) + jj]) entropy[ii] |= 1 << (7 - jj);
109 |
110 | // Take the digest of the entropy.
111 | Uint8List hash = crypto.singleDigest(entropy);
112 | List hashBits = _bytesToBits(hash);
113 |
114 | // Check all the checksum bits.
115 | for (int i = 0; i < checksumLengthBits; ++i)
116 | if (concatBits[entropyLengthBits + i] != hashBits[i]) throw new MnemonicException.checksum();
117 |
118 | return entropy;
119 | }
120 |
121 | /**
122 | * Convert entropy data to mnemonic word list.
123 | */
124 | List toMnemonic(Uint8List entropy) {
125 | if (entropy.length % 4 > 0)
126 | throw new MnemonicException("entropy length not multiple of 32 bits");
127 |
128 | // We take initial entropy of ENT bits and compute its
129 | // checksum by taking first ENT / 32 bits of its SHA256 hash.
130 |
131 | Uint8List hash = crypto.singleDigest(entropy);
132 | List hashBits = _bytesToBits(hash);
133 |
134 | List entropyBits = _bytesToBits(entropy);
135 | int checksumLengthBits = entropyBits.length ~/ 32;
136 |
137 | // We append these bits to the end of the initial entropy.
138 | List concatBits = new List(entropyBits.length + checksumLengthBits)
139 | ..setRange(0, entropyBits.length, entropyBits, 0)
140 | ..setRange(entropyBits.length, entropyBits.length + checksumLengthBits, hashBits, 0);
141 |
142 | // Next we take these concatenated bits and split them into
143 | // groups of 11 bits. Each group encodes number from 0-2047
144 | // which is a position in a wordlist. We convert numbers into
145 | // words and use joined words as mnemonic sentence.
146 |
147 | List words = new List();
148 | int nwords = concatBits.length ~/ 11;
149 | for (int i = 0; i < nwords; ++i) {
150 | int index = 0;
151 | for (int j = 0; j < 11; ++j) {
152 | index <<= 1;
153 | if (concatBits[(i * 11) + j]) index |= 0x1;
154 | }
155 | words.add(_wordList[index]);
156 | }
157 |
158 | return words;
159 | }
160 |
161 | /**
162 | * Check to see if a mnemonic word list is valid.
163 | */
164 | void check(List words) {
165 | toEntropy(words);
166 | }
167 |
168 | List _bytesToBits(Uint8List data) {
169 | List bits = new List(data.length * 8);
170 | for (int i = 0; i < data.length; ++i)
171 | for (int j = 0; j < 8; ++j) bits[(i * 8) + j] = (data[i] & (1 << (7 - j))) != 0;
172 | return bits;
173 | }
174 | }
175 |
176 | class MnemonicException implements Exception {
177 | final String message;
178 |
179 | MnemonicException([this.message]);
180 |
181 | MnemonicException.word(String badWord) : this("Bad word in the mnemonic: $badWord");
182 |
183 | MnemonicException.checksum() : this("Invalid mnemonic checksum");
184 |
185 | @override
186 | String toString() => "MnemonicException: $message";
187 | }
188 |
--------------------------------------------------------------------------------
/test/resources/tx_valid_notworking.json:
--------------------------------------------------------------------------------
1 | [
2 |
3 | ["1"],
4 |
5 | ["The following is 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"],
6 | ["It is of particular interest because it contains an invalidly-encoded signature which OpenSSL accepts"],
7 | ["See http://r6.ca/blog/20111119T211504Z.html"],
8 | ["It is also the first OP_CHECKMULTISIG transaction in standard form"],
9 | [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]],
10 | "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true],
11 |
12 | ["The following is a tweaked form of 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"],
13 | ["It has an arbitrary extra byte stuffed into the signature at pos length - 2"],
14 | [[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]],
15 | "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004A0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true],
16 |
17 |
18 | ["The following is c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73"],
19 | ["It is of interest because it contains a 0-sequence as well as a signature of SIGHASH type 0 (which is not a real type)"],
20 | [[["406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602", 0, "DUP HASH160 0x14 0xdc44b1164188067c3a32d4780f5996fa14a4f2d9 EQUALVERIFY CHECKSIG"]],
21 | "01000000010276b76b07f4935c70acf54fbf1f438a4c397a9fb7e633873c4dd3bc062b6b40000000008c493046022100d23459d03ed7e9511a47d13292d3430a04627de6235b6e51a40f9cd386f2abe3022100e7d25b080f0bb8d8d5f878bba7d54ad2fda650ea8d158a33ee3cbd11768191fd004104b0e2c879e4daf7b9ab68350228c159766676a14f5815084ba166432aab46198d4cca98fa3e9981d0a90b2effc514b76279476550ba3663fdcaff94c38420e9d5000000000100093d00000000001976a9149a7b0f3b80c6baaeedce0a0842553800f832ba1f88ac00000000", true],
22 |
23 | ["A nearly-standard transaction with CHECKSIGVERIFY 1 instead of CHECKSIG"],
24 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1"]],
25 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true],
26 |
27 | ["Same as above, but with the signature duplicated in the scriptPubKey with the proper pushdata prefix"],
28 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]],
29 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true],
30 |
31 | ["The following is f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"],
32 | ["It caught a bug in the workaround for 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63 in an overly simple implementation"],
33 | [[["b464e85df2a238416f8bdae11d120add610380ea07f4ef19c5f9dfd472f96c3d", 0, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"],
34 | ["b7978cc96e59a8b13e0865d3f95657561a7f725be952438637475920bac9eb21", 1, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"]],
35 | "01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000", true],
36 |
37 | ["The following tests for the presence of a bug in the handling of SIGHASH_SINGLE"],
38 | ["It results in signing the constant 1, instead of something generated based on the transaction,"],
39 | ["when the input doing the signing has an index greater than the maximum output index"],
40 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0xe52b482f2faa8ecbf0db344f93c84ac908557f33 EQUALVERIFY CHECKSIG"], ["0000000000000000000000000000000000000000000000000000000000000200", 0, "1"]],
41 | "01000000020002000000000000000000000000000000000000000000000000000000000000000000000151ffffffff0001000000000000000000000000000000000000000000000000000000000000000000006b483045022100c9cdd08798a28af9d1baf44a6c77bcc7e279f47dc487c8c899911bc48feaffcc0220503c5c50ae3998a733263c5c0f7061b483e2b56c4c41b456e7d2f5a78a74c077032102d5c25adb51b61339d2b05315791e21bbe80ea470a49db0135720983c905aace0ffffffff010000000000000000015100000000", true],
42 |
43 | ["2"],
44 |
45 | ["A valid P2SH Transaction using the standard transaction type put forth in BIP 16"],
46 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x8febbed40483661de6958d957412f82deed8e2f7 EQUAL"]],
47 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100c66c9cdf4c43609586d15424c54707156e316d88b0a1534c9e6b0d4f311406310221009c0fe51dbc9c4ab7cc25d3fdbeccf6679fe6827f08edf2b4a9f16ee3eb0e438a0123210338e8034509af564c62644c07691942e0c056752008a173c89f60ab2a88ac2ebfacffffffff010000000000000000015100000000", true],
48 |
49 |
50 | ["Tests for CheckTransaction()"],
51 | ["MAX_MONEY output"],
52 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x32afac281462b822adbec5094b8d4d337dd5bd6a EQUAL"]],
53 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010040075af0750700015100000000", true],
54 |
55 |
56 | ["MAX_MONEY output + 0 output"],
57 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xb558cbf4930954aa6a344363a15668d7477ae716 EQUAL"]],
58 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510000000000000000015100000000", true],
59 |
60 |
61 |
62 |
63 | ]
--------------------------------------------------------------------------------
/test/resources/tx_invalid.json:
--------------------------------------------------------------------------------
1 | [
2 | ["The following are deserialized transactions which are invalid."],
3 | ["They are in the form"],
4 | ["[[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"],
5 | ["serializedTransaction, enforceP2SH]"],
6 | ["Objects that are only a single string (like this one) are ignored"],
7 |
8 | ["0e1b5688cf179cd9f7cbda1fac0090f6e684bbf8cd946660120197c3f3681809 but with extra junk appended to the end of the scriptPubKey"],
9 | [[["6ca7ec7b1847f6bdbd737176050e6a08d66ccd55bb94ad24f4018024107a5827", 0, "0x41 0x043b640e983c9690a14c039a2037ecc3467b27a0dcd58f19d76c7bc118d09fec45adc5370a1c5bf8067ca9f5557a4cf885fdb0fe0dcc9c3a7137226106fbc779a5 CHECKSIG VERIFY 1"]],
10 | "010000000127587a10248001f424ad94bb55cd6cd6086a0e05767173bdbdf647187beca76c000000004948304502201b822ad10d6adc1a341ae8835be3f70a25201bbff31f59cbb9c5353a5f0eca18022100ea7b2f7074e9aa9cf70aa8d0ffee13e6b45dddabf1ab961bda378bcdb778fa4701ffffffff0100f2052a010000001976a914fc50c5907d86fed474ba5ce8b12a66e0a4c139d888ac00000000", true],
11 |
12 | ["This is the nearly-standard transaction with CHECKSIGVERIFY 1 instead of CHECKSIG from tx_valid.json"],
13 | ["but with the signature duplicated in the scriptPubKey with a non-standard pushdata prefix"],
14 | ["See FindAndDelete, which will only remove if it uses the same pushdata prefix as is standard"],
15 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x4c 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]],
16 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true],
17 |
18 | ["Same as above, but with the sig in the scriptSig also pushed with the same non-standard OP_PUSHDATA"],
19 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x4c 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]],
20 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006b4c473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true],
21 |
22 | ["An invalid P2SH Transaction"],
23 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]],
24 | "010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", true],
25 |
26 | ["Tests for CheckTransaction()"],
27 | ["No inputs"],
28 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]],
29 | "0100000000010000000000000000015100000000", true],
30 |
31 | ["No outputs"],
32 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x05ab9e14d983742513f0f451e105ffb4198d1dd4 EQUAL"]],
33 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022100f16703104aab4e4088317c862daec83440242411b039d14280e03dd33b487ab802201318a7be236672c5c56083eb7a5a195bc57a40af7923ff8545016cd3b571e2a601232103c40e5d339df3f30bf753e7e04450ae4ef76c9e45587d1d993bdc4cd06f0651c7acffffffff0000000000", true],
34 |
35 | ["Negative output"],
36 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xae609aca8061d77c5e111f6bb62501a6bbe2bfdb EQUAL"]],
37 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006d4830450220063222cbb128731fc09de0d7323746539166544d6c1df84d867ccea84bcc8903022100bf568e8552844de664cd41648a031554327aa8844af34b4f27397c65b92c04de0123210243ec37dee0e2e053a9c976f43147e79bc7d9dc606ea51010af1ac80db6b069e1acffffffff01ffffffffffffffff015100000000", true],
38 |
39 | ["MAX_MONEY + 1 output"],
40 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x32afac281462b822adbec5094b8d4d337dd5bd6a EQUAL"]],
41 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006e493046022100e1eadba00d9296c743cb6ecc703fd9ddc9b3cd12906176a226ae4c18d6b00796022100a71aef7d2874deff681ba6080f1b278bac7bb99c61b08a85f4311970ffe7f63f012321030c0588dc44d92bdcbf8e72093466766fdc265ead8db64517b0c542275b70fffbacffffffff010140075af0750700015100000000", true],
42 |
43 | ["MAX_MONEY output + 1 output"],
44 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0xb558cbf4930954aa6a344363a15668d7477ae716 EQUAL"]],
45 | "01000000010001000000000000000000000000000000000000000000000000000000000000000000006d483045022027deccc14aa6668e78a8c9da3484fbcd4f9dcc9bb7d1b85146314b21b9ae4d86022100d0b43dece8cfb07348de0ca8bc5b86276fa88f7f2138381128b7c36ab2e42264012321029bb13463ddd5d2cc05da6e84e37536cb9525703cfd8f43afdb414988987a92f6acffffffff020040075af075070001510001000000000000015100000000", true],
46 |
47 | ["Duplicate inputs"],
48 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x236d0639db62b0773fd8ac34dc85ae19e9aba80a EQUAL"]],
49 | "01000000020001000000000000000000000000000000000000000000000000000000000000000000006c47304402204bb1197053d0d7799bf1b30cd503c44b58d6240cccbdc85b6fe76d087980208f02204beeed78200178ffc6c74237bb74b3f276bbb4098b5605d814304fe128bf1431012321039e8815e15952a7c3fada1905f8cf55419837133bd7756c0ef14fc8dfe50c0deaacffffffff0001000000000000000000000000000000000000000000000000000000000000000000006c47304402202306489afef52a6f62e90bf750bbcdf40c06f5c6b138286e6b6b86176bb9341802200dba98486ea68380f47ebb19a7df173b99e6bc9c681d6ccf3bde31465d1f16b3012321039e8815e15952a7c3fada1905f8cf55419837133bd7756c0ef14fc8dfe50c0deaacffffffff010000000000000000015100000000", true],
50 |
51 | ["Coinbase of size 1"],
52 | ["Note the input is just required to make the tester happy"],
53 | [[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]],
54 | "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0151ffffffff010000000000000000015100000000", true],
55 |
56 | ["Coinbase of size 101"],
57 | ["Note the input is just required to make the tester happy"],
58 | [[["0000000000000000000000000000000000000000000000000000000000000000", -1, "1"]],
59 | "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff655151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151515151ffffffff010000000000000000015100000000", true],
60 |
61 | ["Null txin"],
62 | [[["0000000000000000000000000000000000000000000000000000000000000000", -1, "HASH160 0x14 0x02dae7dbbda56097959cba59b1989dd3e47937bf EQUAL"]],
63 | "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff6e49304602210086f39e028e46dafa8e1e3be63906465f4cf038fbe5ed6403dc3e74ae876e6431022100c4625c675cfc5c7e3a0e0d7eaec92ac24da20c73a88eb40d09253e51ac6def5201232103a183ddc41e84753aca47723c965d1b5c8b0e2b537963518355e6dd6cf8415e50acffffffff010000000000000000015100000000", true],
64 |
65 | ["Same as the transactions in valid with one input SIGHASH_ALL and one SIGHASH_ANYONECANPAY, but we set the _ANYONECANPAY sequence number, invalidating the SIGHASH_ALL signature"],
66 | [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x21 0x035e7f0d4d0841bcd56c39337ed086b1a633ee770c1ffdd94ac552a95ac2ce0efc CHECKSIG"],
67 | ["0000000000000000000000000000000000000000000000000000000000000200", 0, "0x21 0x035e7f0d4d0841bcd56c39337ed086b1a633ee770c1ffdd94ac552a95ac2ce0efc CHECKSIG"]],
68 | "01000000020001000000000000000000000000000000000000000000000000000000000000000000004948304502203a0f5f0e1f2bdbcd04db3061d18f3af70e07f4f467cbc1b8116f267025f5360b022100c792b6e215afc5afc721a351ec413e714305cb749aae3d7fee76621313418df10101000000000200000000000000000000000000000000000000000000000000000000000000000000484730440220201dc2d030e380e8f9cfb41b442d930fa5a685bb2c8db5906671f865507d0670022018d9e7a8d4c8d86a73c2a724ee38ef983ec249827e0e464841735955c707ece98101000000010100000000000000015100000000", true],
69 |
70 | ["Incorrect signature order"],
71 | ["Note the input is just required to make the tester happy"],
72 | [[["b3da01dd4aae683c7aee4d5d8b52a540a508e1115f77cd7fa9a291243f501223", 0, "HASH160 0x14 0xb1ce99298d5f07364b57b1e5c9cc00be0b04a954 EQUAL"]],
73 | "01000000012312503f2491a2a97fcd775f11e108a540a5528b5d4dee7a3c68ae4add01dab300000000fdfe000048304502207aacee820e08b0b174e248abd8d7a34ed63b5da3abedb99934df9fddd65c05c4022100dfe87896ab5ee3df476c2655f9fbe5bd089dccbef3e4ea05b5d121169fe7f5f401483045022100f6649b0eddfdfd4ad55426663385090d51ee86c3481bdc6b0c18ea6c0ece2c0b0220561c315b07cffa6f7dd9df96dbae9200c2dee09bf93cc35ca05e6cdf613340aa014c695221031d11db38972b712a9fe1fc023577c7ae3ddb4a3004187d41c45121eecfdbb5b7210207ec36911b6ad2382860d32989c7b8728e9489d7bbc94a6b5509ef0029be128821024ea9fac06f666a4adc3fc1357b7bec1fd0bdece2b9d08579226a8ebde53058e453aeffffffff0180380100000000001976a914c9b99cddf847d10685a4fabaa0baf505f7c3dfab88ac00000000", true],
74 |
75 | ["Empty stack when we try to run CHECKSIG"],
76 | [[["ad503f72c18df5801ee64d76090afe4c607fb2b822e9b7b63c5826c50e22fc3b", 0, "0x21 0x027c3a97665bf283a102a587a62a30a0c102d4d3b141015e2cae6f64e2543113e5 CHECKSIG NOT"]],
77 | "01000000013bfc220ec526583cb6b7e922b8b27f604cfe0a09764de61e80f58dc1723f50ad0000000000ffffffff0101000000000000002321027c3a97665bf283a102a587a62a30a0c102d4d3b141015e2cae6f64e2543113e5ac00000000", true],
78 |
79 | ["Make diffs cleaner by leaving a comment here without comma at the end"]
80 | ]
81 |
--------------------------------------------------------------------------------
/lib/src/core/bloom_filter.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.core;
2 |
3 | /**
4 | * A Bloom filter is a probabilistic data structure which can be sent to another client so that it can avoid
5 | * sending us transactions that aren't relevant to our set of keys. This allows for significantly more efficient
6 | * use of available network bandwidth and CPU time.
7 | *
8 | * Because a Bloom filter is probabilistic, it has a configurable false positive rate. So the filter will sometimes
9 | * match transactions that weren't inserted into it, but it will never fail to match transactions that were. This is
10 | * a useful privacy feature - if you have spare bandwidth the false positive rate can be increased so the remote peer
11 | * gets a noisy picture of what transactions are relevant to your wallet.
12 | */
13 | class BloomFilter extends BitcoinSerializable {
14 | Uint8List data;
15 | int hashFuncs;
16 | int nTweak;
17 | int nFlags;
18 |
19 | // Same value as the reference client
20 | // A filter of 20,000 items and a false positive rate of 0.1% or one of 10,000 items and 0.0001% is just under 36,000 bytes
21 | static const int MAX_FILTER_SIZE = 36000;
22 | // There is little reason to ever have more hash functions than 50 given a limit of 36,000 bytes
23 | static const int MAX_HASH_FUNCS = 50;
24 |
25 | /**
26 | * Constructs a new Bloom Filter which will provide approximately the given false positive
27 | * rate when the given number of elements have been inserted.
28 | *
29 | * If the filter would otherwise be larger than the maximum allowed size, it will be
30 | * automatically downsized to the maximum size.
31 | *
32 | * To check the theoretical false positive rate of a given filter, use {@link BloomFilter#getFalsePositiveRate(int)}
33 | *
34 | * The anonymity of which coins are yours to any peer which you send a BloomFilter to is
35 | * controlled by the false positive rate.
36 | *
37 | * For reference, as of block 187,000, the total number of addresses used in the chain was roughly 4.5 million.
38 | *
39 | * Thus, if you use a false positive rate of 0.001 (0.1%), there will be, on average, 4,500 distinct public
40 | * keys/addresses which will be thought to be yours by nodes which have your bloom filter, but which are not
41 | * actually yours.
42 | *
43 | * Keep in mind that a remote node can do a pretty good job estimating the order of magnitude of the false positive
44 | * rate of a given filter you provide it when considering the anonymity of a given filter.
45 | *
46 | * In order for filtered block download to function efficiently, the number of matched transactions in any given
47 | * block should be less than (with some headroom) the maximum size of the MemoryPool used by the Peer
48 | * doing the downloading (default is {@link MemoryPool#MAX_SIZE}). See the comment in processBlock(FilteredBlock)
49 | * for more information on this restriction.
50 | *
51 | * randomNonce is a tweak for the hash function used to prevent some theoretical DoS attacks.
52 | * It should be a random value, however secureness of the random value is of no great consequence.
53 | *
54 | * updateFlag is used to control filter behavior
55 | */
56 | BloomFilter(int elements, double falsePositiveRate, int randomNonce,
57 | [BloomUpdate updateFlag = BloomUpdate.UPDATE_P2PUBKEY_ONLY]) {
58 | if (elements == null || falsePositiveRate == null || randomNonce == null)
59 | throw new ArgumentError("The required arguments should not be null");
60 | // The following formulas were stolen from Wikipedia's page on Bloom Filters (with the addition of min(..., MAX_...))
61 | // Size required for a given number of elements and false-positive rate
62 | int size = min((-1 / (pow(log(2), 2)) * elements * log(falsePositiveRate)).floor(),
63 | MAX_FILTER_SIZE * 8) ~/
64 | 8;
65 | data = new Uint8List(size <= 0 ? 1 : size);
66 | // Optimal number of hash functions for a given filter size and element count.
67 | hashFuncs = min((data.length * 8 / elements * log(2)).floor(), MAX_HASH_FUNCS);
68 | nTweak = randomNonce;
69 | nFlags = (0xff & updateFlag.index);
70 | }
71 |
72 | /// Create an empty instance.
73 | BloomFilter.empty();
74 |
75 | /**
76 | * Returns the theoretical false positive rate of this filter if were to contain the given number of elements.
77 | */
78 | double getFalsePositiveRate(int elements) {
79 | return pow(1 - pow(E, -1.0 * (hashFuncs * elements) / (data.length * 8)), hashFuncs);
80 | }
81 |
82 | @override
83 | String toString() => "Bloom Filter of size ${data.length} with $hashFuncs hash functions.";
84 |
85 | void bitcoinDeserialize(bytes.Reader reader, int pver) {
86 | data = readByteArray(reader);
87 | if (data.length > MAX_FILTER_SIZE)
88 | throw new SerializationException("Bloom filter out of size range.");
89 | hashFuncs = readUintLE(reader);
90 | if (hashFuncs > MAX_HASH_FUNCS)
91 | throw new SerializationException("Bloom filter hash function count out of range");
92 | nTweak = readUintLE(reader);
93 | nFlags = readUintLE(reader, 1);
94 | }
95 |
96 | void bitcoinSerialize(bytes.Buffer buffer, int pver) {
97 | writeByteArray(buffer, data);
98 | writeUintLE(buffer, hashFuncs);
99 | writeUintLE(buffer, nTweak);
100 | writeBytes(buffer, [nFlags]);
101 | }
102 |
103 | static int _rotateLeft32(int x, int r) {
104 | return (x << r) | utils.lsr(x, 32 - r);
105 | }
106 |
107 | int _hash(int hashNum, Uint8List object) {
108 | // The following is MurmurHash3 (x86_32), see http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
109 | // implementation copied from BitcoinJ
110 | //TODO implement in Pointy Castle?
111 | int h1 = hashNum * 0xFBA4C795 + nTweak;
112 | final int c1 = 0xcc9e2d51;
113 | final int c2 = 0x1b873593;
114 |
115 | int numBlocks = (object.length ~/ 4) * 4;
116 | // body
117 | for (int i = 0; i < numBlocks; i += 4) {
118 | int k1 = (object[i] & 0xFF) |
119 | ((object[i + 1] & 0xFF) << 8) |
120 | ((object[i + 2] & 0xFF) << 16) |
121 | ((object[i + 3] & 0xFF) << 24);
122 |
123 | k1 *= c1;
124 | k1 = _rotateLeft32(k1, 15);
125 | k1 *= c2;
126 |
127 | h1 ^= k1;
128 | h1 = _rotateLeft32(h1, 13);
129 | h1 = h1 * 5 + 0xe6546b64;
130 | }
131 |
132 | int k1 = 0;
133 | if ((object.length & 3) >= 3) k1 ^= (object[numBlocks + 2] & 0xff) << 16;
134 | if ((object.length & 3) >= 2) k1 ^= (object[numBlocks + 1] & 0xff) << 8;
135 | if ((object.length & 3) >= 1) {
136 | k1 ^= (object[numBlocks] & 0xff);
137 | k1 *= c1;
138 | k1 = _rotateLeft32(k1, 15);
139 | k1 *= c2;
140 | h1 ^= k1;
141 | }
142 |
143 | // finalization
144 | h1 ^= object.length;
145 | h1 ^= utils.lsr(h1, 16);
146 | h1 *= 0x85ebca6b;
147 | h1 ^= utils.lsr(h1, 13);
148 | h1 *= 0xc2b2ae35;
149 | h1 ^= utils.lsr(h1, 16);
150 |
151 | return ((h1 & 0xFFFFFFFF) % (data.length * 8));
152 | }
153 |
154 | /**
155 | * Returns true if the given object matches the filter
156 | * (either because it was inserted, or because we have a false-positive)
157 | */
158 | bool contains(Uint8List object) {
159 | for (int i = 0; i < hashFuncs; i++) {
160 | if (!utils.checkBitLE(data, _hash(i, object))) return false;
161 | }
162 | return true;
163 | }
164 |
165 | /**
166 | * Insert the given arbitrary data into the filter
167 | */
168 | void insert(Uint8List object) {
169 | for (int i = 0; i < hashFuncs; i++) {
170 | utils.setBitLE(data, _hash(i, object));
171 | }
172 | }
173 |
174 | /**
175 | * Sets this filter to match all objects. A Bloom filter which matches everything may seem pointless, however,
176 | * it is useful in order to reduce steady state bandwidth usage when you want full blocks. Instead of receiving
177 | * all transaction data twice, you will receive the vast majority of all transactions just once, at broadcast time.
178 | * Solved blocks will then be send just as Merkle trees of tx hashes, meaning a constant 32 bytes of data for each
179 | * transaction instead of 100-300 bytes as per usual.
180 | */
181 | void setMatchAll() {
182 | data = new Uint8List.fromList([0xff]);
183 | }
184 |
185 | /**
186 | * Copies filter into this. Filter must have the same size, hash function count and nTweak or an
187 | * IllegalArgumentException will be thrown.
188 | */
189 | void merge(BloomFilter filter) {
190 | if (!this.matchesAll() && !filter.matchesAll()) {
191 | if (!(filter.data.length == data.length &&
192 | filter.hashFuncs == hashFuncs &&
193 | filter.nTweak == nTweak)) {
194 | throw new Exception("Invalid filter passed as parameter; read the docs.");
195 | }
196 | for (int i = 0; i < data.length; i++) data[i] |= filter.data[i];
197 | } else {
198 | data = new Uint8List.fromList([0xff]);
199 | }
200 | }
201 |
202 | /**
203 | * Returns true if this filter will match anything. See {@link com.google.bitcoin.core.BloomFilter#setMatchAll()}
204 | * for when this can be a useful thing to do.
205 | */
206 | bool matchesAll() {
207 | for (int b in data) {
208 | if (b != 0xff) {
209 | return false;
210 | }
211 | }
212 | return true;
213 | }
214 |
215 | @override
216 | bool operator ==(dynamic other) {
217 | if (other.runtimeType != BloomFilter) return false;
218 | if (identical(this, other)) return true;
219 | return other.hashFuncs == this.hashFuncs &&
220 | other.nTweak == this.nTweak &&
221 | utils.equalLists(other.data, this.data);
222 | }
223 |
224 | @override
225 | int get hashCode {
226 | return hashFuncs ^ nTweak ^ utils.listHashCode(data);
227 | }
228 | }
229 |
230 | /** The BLOOM_UPDATE_* constants control when the bloom filter is auto-updated by the peer using
231 | it as a filter, either never, for all outputs or only for pay-2-pubkey outputs (default) */
232 | class BloomUpdate {
233 | static const BloomUpdate UPDATE_NONE = const BloomUpdate._(0);
234 | static const BloomUpdate UPDATE_ALL = const BloomUpdate._(1);
235 | /** Only adds outpoints to the filter if the output is a pay-to-pubkey/pay-to-multisig script */
236 | static const BloomUpdate UPDATE_P2PUBKEY_ONLY = const BloomUpdate._(2);
237 |
238 | final int index;
239 | const BloomUpdate._(int this.index);
240 | }
241 |
--------------------------------------------------------------------------------
/lib/src/script/script_op_codes.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.script;
2 |
3 | class ScriptOpCodes {
4 | // push value
5 | static const int OP_0 = 0x00;
6 | static const int OP_FALSE = OP_0;
7 | static const int OP_PUSHDATA1 = 0x4c;
8 | static const int OP_PUSHDATA2 = 0x4d;
9 | static const int OP_PUSHDATA4 = 0x4e;
10 | static const int OP_1NEGATE = 0x4f;
11 | static const int OP_RESERVED = 0x50;
12 | static const int OP_1 = 0x51;
13 | static const int OP_TRUE = OP_1;
14 | static const int OP_2 = 0x52;
15 | static const int OP_3 = 0x53;
16 | static const int OP_4 = 0x54;
17 | static const int OP_5 = 0x55;
18 | static const int OP_6 = 0x56;
19 | static const int OP_7 = 0x57;
20 | static const int OP_8 = 0x58;
21 | static const int OP_9 = 0x59;
22 | static const int OP_10 = 0x5a;
23 | static const int OP_11 = 0x5b;
24 | static const int OP_12 = 0x5c;
25 | static const int OP_13 = 0x5d;
26 | static const int OP_14 = 0x5e;
27 | static const int OP_15 = 0x5f;
28 | static const int OP_16 = 0x60;
29 |
30 | // control
31 | static const int OP_NOP = 0x61;
32 | static const int OP_VER = 0x62;
33 | static const int OP_IF = 0x63;
34 | static const int OP_NOTIF = 0x64;
35 | static const int OP_VERIF = 0x65;
36 | static const int OP_VERNOTIF = 0x66;
37 | static const int OP_ELSE = 0x67;
38 | static const int OP_ENDIF = 0x68;
39 | static const int OP_VERIFY = 0x69;
40 | static const int OP_RETURN = 0x6a;
41 |
42 | // stack ops
43 | static const int OP_TOALTSTACK = 0x6b;
44 | static const int OP_FROMALTSTACK = 0x6c;
45 | static const int OP_2DROP = 0x6d;
46 | static const int OP_2DUP = 0x6e;
47 | static const int OP_3DUP = 0x6f;
48 | static const int OP_2OVER = 0x70;
49 | static const int OP_2ROT = 0x71;
50 | static const int OP_2SWAP = 0x72;
51 | static const int OP_IFDUP = 0x73;
52 | static const int OP_DEPTH = 0x74;
53 | static const int OP_DROP = 0x75;
54 | static const int OP_DUP = 0x76;
55 | static const int OP_NIP = 0x77;
56 | static const int OP_OVER = 0x78;
57 | static const int OP_PICK = 0x79;
58 | static const int OP_ROLL = 0x7a;
59 | static const int OP_ROT = 0x7b;
60 | static const int OP_SWAP = 0x7c;
61 | static const int OP_TUCK = 0x7d;
62 |
63 | // splice ops
64 | static const int OP_CAT = 0x7e;
65 | static const int OP_SUBSTR = 0x7f;
66 | static const int OP_LEFT = 0x80;
67 | static const int OP_RIGHT = 0x81;
68 | static const int OP_SIZE = 0x82;
69 |
70 | // bit logic
71 | static const int OP_INVERT = 0x83;
72 | static const int OP_AND = 0x84;
73 | static const int OP_OR = 0x85;
74 | static const int OP_XOR = 0x86;
75 | static const int OP_EQUAL = 0x87;
76 | static const int OP_EQUALVERIFY = 0x88;
77 | static const int OP_RESERVED1 = 0x89;
78 | static const int OP_RESERVED2 = 0x8a;
79 |
80 | // numeric
81 | static const int OP_1ADD = 0x8b;
82 | static const int OP_1SUB = 0x8c;
83 | static const int OP_2MUL = 0x8d;
84 | static const int OP_2DIV = 0x8e;
85 | static const int OP_NEGATE = 0x8f;
86 | static const int OP_ABS = 0x90;
87 | static const int OP_NOT = 0x91;
88 | static const int OP_0NOTEQUAL = 0x92;
89 | static const int OP_ADD = 0x93;
90 | static const int OP_SUB = 0x94;
91 | static const int OP_MUL = 0x95;
92 | static const int OP_DIV = 0x96;
93 | static const int OP_MOD = 0x97;
94 | static const int OP_LSHIFT = 0x98;
95 | static const int OP_RSHIFT = 0x99;
96 | static const int OP_BOOLAND = 0x9a;
97 | static const int OP_BOOLOR = 0x9b;
98 | static const int OP_NUMEQUAL = 0x9c;
99 | static const int OP_NUMEQUALVERIFY = 0x9d;
100 | static const int OP_NUMNOTEQUAL = 0x9e;
101 | static const int OP_LESSTHAN = 0x9f;
102 | static const int OP_GREATERTHAN = 0xa0;
103 | static const int OP_LESSTHANOREQUAL = 0xa1;
104 | static const int OP_GREATERTHANOREQUAL = 0xa2;
105 | static const int OP_MIN = 0xa3;
106 | static const int OP_MAX = 0xa4;
107 | static const int OP_WITHIN = 0xa5;
108 |
109 | // crypto
110 | static const int OP_RIPEMD160 = 0xa6;
111 | static const int OP_SHA1 = 0xa7;
112 | static const int OP_SHA256 = 0xa8;
113 | static const int OP_HASH160 = 0xa9;
114 | static const int OP_HASH256 = 0xaa;
115 | static const int OP_CODESEPARATOR = 0xab;
116 | static const int OP_CHECKSIG = 0xac;
117 | static const int OP_CHECKSIGVERIFY = 0xad;
118 | static const int OP_CHECKMULTISIG = 0xae;
119 | static const int OP_CHECKMULTISIGVERIFY = 0xaf;
120 |
121 | // expansion
122 | static const int OP_NOP1 = 0xb0;
123 | static const int OP_NOP2 = 0xb1;
124 | static const int OP_NOP3 = 0xb2;
125 | static const int OP_NOP4 = 0xb3;
126 | static const int OP_NOP5 = 0xb4;
127 | static const int OP_NOP6 = 0xb5;
128 | static const int OP_NOP7 = 0xb6;
129 | static const int OP_NOP8 = 0xb7;
130 | static const int OP_NOP9 = 0xb8;
131 | static const int OP_NOP10 = 0xb9;
132 | static const int OP_INVALIDOPCODE = 0xff;
133 |
134 | static final Map _opCodeMap = {
135 | OP_0: "0",
136 | OP_PUSHDATA1: "PUSHDATA1",
137 | OP_PUSHDATA2: "PUSHDATA2",
138 | OP_PUSHDATA4: "PUSHDATA4",
139 | OP_1NEGATE: "1NEGATE",
140 | OP_RESERVED: "RESERVED",
141 | OP_1: "1",
142 | OP_2: "2",
143 | OP_3: "3",
144 | OP_4: "4",
145 | OP_5: "5",
146 | OP_6: "6",
147 | OP_7: "7",
148 | OP_8: "8",
149 | OP_9: "9",
150 | OP_10: "10",
151 | OP_11: "11",
152 | OP_12: "12",
153 | OP_13: "13",
154 | OP_14: "14",
155 | OP_15: "15",
156 | OP_16: "16",
157 | OP_NOP: "NOP",
158 | OP_VER: "VER",
159 | OP_IF: "IF",
160 | OP_NOTIF: "NOTIF",
161 | OP_VERIF: "VERIF",
162 | OP_VERNOTIF: "VERNOTIF",
163 | OP_ELSE: "ELSE",
164 | OP_ENDIF: "ENDIF",
165 | OP_VERIFY: "VERIFY",
166 | OP_RETURN: "RETURN",
167 | OP_TOALTSTACK: "TOALTSTACK",
168 | OP_FROMALTSTACK: "FROMALTSTACK",
169 | OP_2DROP: "2DROP",
170 | OP_2DUP: "2DUP",
171 | OP_3DUP: "3DUP",
172 | OP_2OVER: "2OVER",
173 | OP_2ROT: "2ROT",
174 | OP_2SWAP: "2SWAP",
175 | OP_IFDUP: "IFDUP",
176 | OP_DEPTH: "DEPTH",
177 | OP_DROP: "DROP",
178 | OP_DUP: "DUP",
179 | OP_NIP: "NIP",
180 | OP_OVER: "OVER",
181 | OP_PICK: "PICK",
182 | OP_ROLL: "ROLL",
183 | OP_ROT: "ROT",
184 | OP_SWAP: "SWAP",
185 | OP_TUCK: "TUCK",
186 | OP_CAT: "CAT",
187 | OP_SUBSTR: "SUBSTR",
188 | OP_LEFT: "LEFT",
189 | OP_RIGHT: "RIGHT",
190 | OP_SIZE: "SIZE",
191 | OP_INVERT: "INVERT",
192 | OP_AND: "AND",
193 | OP_OR: "OR",
194 | OP_XOR: "XOR",
195 | OP_EQUAL: "EQUAL",
196 | OP_EQUALVERIFY: "EQUALVERIFY",
197 | OP_RESERVED1: "RESERVED1",
198 | OP_RESERVED2: "RESERVED2",
199 | OP_1ADD: "1ADD",
200 | OP_1SUB: "1SUB",
201 | OP_2MUL: "2MUL",
202 | OP_2DIV: "2DIV",
203 | OP_NEGATE: "NEGATE",
204 | OP_ABS: "ABS",
205 | OP_NOT: "NOT",
206 | OP_0NOTEQUAL: "0NOTEQUAL",
207 | OP_ADD: "ADD",
208 | OP_SUB: "SUB",
209 | OP_MUL: "MUL",
210 | OP_DIV: "DIV",
211 | OP_MOD: "MOD",
212 | OP_LSHIFT: "LSHIFT",
213 | OP_RSHIFT: "RSHIFT",
214 | OP_BOOLAND: "BOOLAND",
215 | OP_BOOLOR: "BOOLOR",
216 | OP_NUMEQUAL: "NUMEQUAL",
217 | OP_NUMEQUALVERIFY: "NUMEQUALVERIFY",
218 | OP_NUMNOTEQUAL: "NUMNOTEQUAL",
219 | OP_LESSTHAN: "LESSTHAN",
220 | OP_GREATERTHAN: "GREATERTHAN",
221 | OP_LESSTHANOREQUAL: "LESSTHANOREQUAL",
222 | OP_GREATERTHANOREQUAL: "GREATERTHANOREQUAL",
223 | OP_MIN: "MIN",
224 | OP_MAX: "MAX",
225 | OP_WITHIN: "WITHIN",
226 | OP_RIPEMD160: "RIPEMD160",
227 | OP_SHA1: "SHA1",
228 | OP_SHA256: "SHA256",
229 | OP_HASH160: "HASH160",
230 | OP_HASH256: "HASH256",
231 | OP_CODESEPARATOR: "CODESEPARATOR",
232 | OP_CHECKSIG: "CHECKSIG",
233 | OP_CHECKSIGVERIFY: "CHECKSIGVERIFY",
234 | OP_CHECKMULTISIG: "CHECKMULTISIG",
235 | OP_CHECKMULTISIGVERIFY: "CHECKMULTISIGVERIFY",
236 | OP_NOP1: "NOP1",
237 | OP_NOP2: "NOP2",
238 | OP_NOP3: "NOP3",
239 | OP_NOP4: "NOP4",
240 | OP_NOP5: "NOP5",
241 | OP_NOP6: "NOP6",
242 | OP_NOP7: "NOP7",
243 | OP_NOP8: "NOP8",
244 | OP_NOP9: "NOP9",
245 | OP_NOP10: "NOP10"
246 | };
247 |
248 | static final Map _opCodeNameMap = {
249 | "0": OP_0,
250 | "PUSHDATA1": OP_PUSHDATA1,
251 | "PUSHDATA2": OP_PUSHDATA2,
252 | "PUSHDATA4": OP_PUSHDATA4,
253 | "1NEGATE": OP_1NEGATE,
254 | "RESERVED": OP_RESERVED,
255 | "1": OP_1,
256 | "2": OP_2,
257 | "3": OP_3,
258 | "4": OP_4,
259 | "5": OP_5,
260 | "6": OP_6,
261 | "7": OP_7,
262 | "8": OP_8,
263 | "9": OP_9,
264 | "10": OP_10,
265 | "11": OP_11,
266 | "12": OP_12,
267 | "13": OP_13,
268 | "14": OP_14,
269 | "15": OP_15,
270 | "16": OP_16,
271 | "NOP": OP_NOP,
272 | "VER": OP_VER,
273 | "IF": OP_IF,
274 | "NOTIF": OP_NOTIF,
275 | "VERIF": OP_VERIF,
276 | "VERNOTIF": OP_VERNOTIF,
277 | "ELSE": OP_ELSE,
278 | "ENDIF": OP_ENDIF,
279 | "VERIFY": OP_VERIFY,
280 | "RETURN": OP_RETURN,
281 | "TOALTSTACK": OP_TOALTSTACK,
282 | "FROMALTSTACK": OP_FROMALTSTACK,
283 | "2DROP": OP_2DROP,
284 | "2DUP": OP_2DUP,
285 | "3DUP": OP_3DUP,
286 | "2OVER": OP_2OVER,
287 | "2ROT": OP_2ROT,
288 | "2SWAP": OP_2SWAP,
289 | "IFDUP": OP_IFDUP,
290 | "DEPTH": OP_DEPTH,
291 | "DROP": OP_DROP,
292 | "DUP": OP_DUP,
293 | "NIP": OP_NIP,
294 | "OVER": OP_OVER,
295 | "PICK": OP_PICK,
296 | "ROLL": OP_ROLL,
297 | "ROT": OP_ROT,
298 | "SWAP": OP_SWAP,
299 | "TUCK": OP_TUCK,
300 | "CAT": OP_CAT,
301 | "SUBSTR": OP_SUBSTR,
302 | "LEFT": OP_LEFT,
303 | "RIGHT": OP_RIGHT,
304 | "SIZE": OP_SIZE,
305 | "INVERT": OP_INVERT,
306 | "AND": OP_AND,
307 | "OR": OP_OR,
308 | "XOR": OP_XOR,
309 | "EQUAL": OP_EQUAL,
310 | "EQUALVERIFY": OP_EQUALVERIFY,
311 | "RESERVED1": OP_RESERVED1,
312 | "RESERVED2": OP_RESERVED2,
313 | "1ADD": OP_1ADD,
314 | "1SUB": OP_1SUB,
315 | "2MUL": OP_2MUL,
316 | "2DIV": OP_2DIV,
317 | "NEGATE": OP_NEGATE,
318 | "ABS": OP_ABS,
319 | "NOT": OP_NOT,
320 | "0NOTEQUAL": OP_0NOTEQUAL,
321 | "ADD": OP_ADD,
322 | "SUB": OP_SUB,
323 | "MUL": OP_MUL,
324 | "DIV": OP_DIV,
325 | "MOD": OP_MOD,
326 | "LSHIFT": OP_LSHIFT,
327 | "RSHIFT": OP_RSHIFT,
328 | "BOOLAND": OP_BOOLAND,
329 | "BOOLOR": OP_BOOLOR,
330 | "NUMEQUAL": OP_NUMEQUAL,
331 | "NUMEQUALVERIFY": OP_NUMEQUALVERIFY,
332 | "NUMNOTEQUAL": OP_NUMNOTEQUAL,
333 | "LESSTHAN": OP_LESSTHAN,
334 | "GREATERTHAN": OP_GREATERTHAN,
335 | "LESSTHANOREQUAL": OP_LESSTHANOREQUAL,
336 | "GREATERTHANOREQUAL": OP_GREATERTHANOREQUAL,
337 | "MIN": OP_MIN,
338 | "MAX": OP_MAX,
339 | "WITHIN": OP_WITHIN,
340 | "RIPEMD160": OP_RIPEMD160,
341 | "SHA1": OP_SHA1,
342 | "SHA256": OP_SHA256,
343 | "HASH160": OP_HASH160,
344 | "HASH256": OP_HASH256,
345 | "CODESEPARATOR": OP_CODESEPARATOR,
346 | "CHECKSIG": OP_CHECKSIG,
347 | "CHECKSIGVERIFY": OP_CHECKSIGVERIFY,
348 | "CHECKMULTISIG": OP_CHECKMULTISIG,
349 | "CHECKMULTISIGVERIFY": OP_CHECKMULTISIGVERIFY,
350 | "NOP1": OP_NOP1,
351 | "NOP2": OP_NOP2,
352 | "NOP3": OP_NOP3,
353 | "NOP4": OP_NOP4,
354 | "NOP5": OP_NOP5,
355 | "NOP6": OP_NOP6,
356 | "NOP7": OP_NOP7,
357 | "NOP8": OP_NOP8,
358 | "NOP9": OP_NOP9,
359 | "NOP10": OP_NOP10
360 | };
361 |
362 | static String getOpCodeName(int opCode) {
363 | if (_opCodeMap.containsKey(opCode)) return _opCodeMap[opCode];
364 | return "NON_OP($opCode)";
365 | }
366 |
367 | static int getOpCode(String opCodeName) {
368 | if (_opCodeNameMap.containsKey(opCodeName)) return _opCodeNameMap[opCodeName];
369 | return OP_INVALIDOPCODE;
370 | }
371 | }
372 |
--------------------------------------------------------------------------------
/lib/src/script/script.dart:
--------------------------------------------------------------------------------
1 | part of bitcoin.script;
2 |
3 | class Script {
4 | static final int MAX_SCRIPT_ELEMENT_SIZE = 520; //bytes
5 | static final Script EMPTY_SCRIPT = new Script(new Uint8List(0));
6 |
7 | List _chunks;
8 | Uint8List _program;
9 |
10 | /**
11 | * Create a new script.
12 | *
13 | * Either a [Uint8List] or an iterable of [ScriptChunk]s must be passed as argument.
14 | */
15 | Script(dynamic script) {
16 | if (script is Uint8List) {
17 | _program = new Uint8List.fromList(script);
18 | return;
19 | }
20 | if (script is Iterable) {
21 | _chunks = new List.from(script, growable: false);
22 | return;
23 | }
24 | throw new ArgumentError(
25 | "Either a [Uint8List] or an iterable of [ScriptChunk]s must be passed as argument.");
26 | }
27 |
28 | factory Script.fromString(String string) {
29 | if (string == null) throw new ArgumentError("parameter should not be null");
30 | List chunks = new List();
31 | for (String s in string.split(" ")) {
32 | if (s == "") continue;
33 | // try int
34 | try {
35 | int val = int.parse(s);
36 | if (val >= -1 && val <= 16) {
37 | chunks.add(new ScriptChunk.opCodeChunk(Script.encodeToOpN(val)));
38 | continue;
39 | }
40 | // else
41 | // Script.writeBytes(out, utils.reverseBytes(utils.encodeMPI(BigInteger.valueOf(val), false)));
42 | } catch (e) {}
43 | // try opcode
44 | if (s.startsWith("OP_")) s = s.substring(3);
45 | int opcode = ScriptOpCodes.getOpCode(s);
46 | if (opcode != ScriptOpCodes.OP_INVALIDOPCODE) {
47 | chunks.add(new ScriptChunk.opCodeChunk(opcode));
48 | continue;
49 | }
50 | // try data
51 | if (s.startsWith("[") && s.endsWith("]")) {
52 | chunks.add(new ScriptChunk.dataChunk(CryptoUtils.hexToBytes(s.substring(1, s.length - 1))));
53 | continue;
54 | }
55 | throw new FormatException("The script string is invalid: $string");
56 | }
57 | return new Script(chunks);
58 | }
59 |
60 | Uint8List get program {
61 | if (_program == null) {
62 | _program = encode();
63 | }
64 | return new Uint8List.fromList(_program);
65 | }
66 |
67 | List get chunks {
68 | if (_chunks == null) {
69 | _parse();
70 | }
71 | return new UnmodifiableListView(_chunks);
72 | }
73 |
74 | @override
75 | operator ==(dynamic other) {
76 | if (other.runtimeType != Script) return false;
77 | if (identical(this, other)) return true;
78 | return utils.equalLists(this.program, other.program);
79 | }
80 |
81 | @override
82 | int get hashCode {
83 | return utils.listHashCode(this.program);
84 | }
85 |
86 | String toString() {
87 | StringBuffer buf = new StringBuffer();
88 | buf.writeAll(chunks, " ");
89 | return buf.toString();
90 | }
91 |
92 | void _parse() {
93 | if (_chunks != null) return;
94 | List chunks = new List();
95 | var reader = new bytes.Reader(_program);
96 | int initialSize = reader.remainingLength;
97 |
98 | while (reader.remainingLength > 0) {
99 | int startLocationInProgram = initialSize - reader.remainingLength;
100 | int opcode = reader.readByte();
101 |
102 | int dataToRead = -1;
103 | if (opcode >= 0 && opcode < ScriptOpCodes.OP_PUSHDATA1) {
104 | dataToRead = opcode;
105 | } else if (opcode == ScriptOpCodes.OP_PUSHDATA1) {
106 | if (reader.remainingLength < 1) {
107 | throw new ScriptException("Unexpected end of script", this, opcode);
108 | }
109 | dataToRead = reader.readByte();
110 | } else if (opcode == ScriptOpCodes.OP_PUSHDATA2) {
111 | if (reader.remainingLength < 2) {
112 | throw new ScriptException("Unexpected end of script", this, opcode);
113 | }
114 | dataToRead = reader.readByte() | (reader.readByte() << 8);
115 | } else if (opcode == ScriptOpCodes.OP_PUSHDATA4) {
116 | if (reader.remainingLength < 4) {
117 | throw new ScriptException("Unexpected end of script", this, opcode);
118 | }
119 | dataToRead = reader.readByte() |
120 | (reader.readByte() << 8) |
121 | (reader.readByte() << 16) |
122 | (reader.readByte() << 24);
123 | }
124 |
125 | if (dataToRead == -1) {
126 | chunks.add(new ScriptChunk.opCodeChunk(opcode, startLocationInProgram));
127 | } else {
128 | if (dataToRead > reader.remainingLength) {
129 | throw new ScriptException(
130 | "Push of data element that is larger than remaining data", this, opcode);
131 | }
132 | chunks.add(new ScriptChunk.dataChunk(reader.readBytes(dataToRead), startLocationInProgram));
133 | }
134 | }
135 | _chunks = chunks;
136 | }
137 |
138 | Uint8List encode() {
139 | if (_program != null) return _program;
140 | var buffer = new bytes.Buffer();
141 | for (ScriptChunk chunk in chunks) {
142 | buffer.add(chunk.serialize());
143 | }
144 | return buffer.asBytes();
145 | }
146 |
147 | static Uint8List encodeData(Uint8List data) {
148 | if (data == null) return new Uint8List(1); // one byte indicating a 0-length sequence
149 | List result = new List();
150 | if (data.length < ScriptOpCodes.OP_PUSHDATA1) {
151 | result.add(data.length);
152 | } else if (data.length <= 0xff) {
153 | result.add(ScriptOpCodes.OP_PUSHDATA1);
154 | result.add(data.length);
155 | } else if (data.length <= 0xffff) {
156 | result.add(ScriptOpCodes.OP_PUSHDATA2);
157 | result.addAll(utils.uintToBytesLE(data.length, 2));
158 | } else {
159 | result.add(ScriptOpCodes.OP_PUSHDATA4);
160 | result.addAll(utils.uintToBytesLE(data.length, 4));
161 | }
162 | result.addAll(data);
163 | return new Uint8List.fromList(result);
164 | }
165 |
166 | static int decodeFromOpN(int opcode) {
167 | if (!(opcode == ScriptOpCodes.OP_0 ||
168 | opcode == ScriptOpCodes.OP_1NEGATE ||
169 | (opcode >= ScriptOpCodes.OP_1 && opcode <= ScriptOpCodes.OP_16))) {
170 | throw new ScriptException("Method should be called on an OP_N opcode.", null, opcode);
171 | }
172 | if (opcode == ScriptOpCodes.OP_0)
173 | return 0;
174 | else if (opcode == ScriptOpCodes.OP_1NEGATE)
175 | return -1;
176 | else
177 | return opcode + 1 - ScriptOpCodes.OP_1;
178 | }
179 |
180 | static int encodeToOpN(int value) {
181 | if (!(value >= -1 && value <= 16)) {
182 | throw new ScriptException("Can only encode for values -1 <= value <= 16.");
183 | }
184 | if (value == 0)
185 | return ScriptOpCodes.OP_0;
186 | else if (value == -1)
187 | return ScriptOpCodes.OP_1NEGATE;
188 | else
189 | return value - 1 + ScriptOpCodes.OP_1;
190 | }
191 |
192 | int get sigOpCount {
193 | int sigOps = 0;
194 | int lastOpCode = ScriptOpCodes.OP_INVALIDOPCODE;
195 | for (ScriptChunk chunk in chunks) {
196 | if (chunk.isOpCode) {
197 | int opcode = 0xFF & chunk.data[0];
198 | switch (opcode) {
199 | case ScriptOpCodes.OP_CHECKSIG:
200 | case ScriptOpCodes.OP_CHECKSIGVERIFY:
201 | sigOps++;
202 | break;
203 | case ScriptOpCodes.OP_CHECKMULTISIG:
204 | case ScriptOpCodes.OP_CHECKMULTISIGVERIFY:
205 | if (lastOpCode >= ScriptOpCodes.OP_1 && lastOpCode <= ScriptOpCodes.OP_16)
206 | sigOps += decodeFromOpN(lastOpCode);
207 | else
208 | sigOps += 20;
209 | break;
210 | default:
211 | break;
212 | }
213 | lastOpCode = opcode;
214 | }
215 | }
216 | return sigOps;
217 | }
218 |
219 | /**
220 | * Verifies that this script (interpreted as a scriptSig) correctly spends the given scriptPubKey.
221 | * @param txContainingThis The transaction in which this input scriptSig resides.
222 | * Accessing txContainingThis from another thread while this method runs results in undefined behavior.
223 | * @param scriptSigIndex The index in txContainingThis of the scriptSig (note: NOT the index of the scriptPubKey).
224 | * @param scriptPubKey The connected scriptPubKey containing the conditions needed to claim the value.
225 | * @param enforceP2SH Whether "pay to script hash" rules should be enforced. If in doubt, set to true.
226 | */
227 | void correctlySpends(
228 | Transaction txContainingThis, int scriptSigIndex, Script scriptPubKey, bool enforceP2SH) {
229 | // Clone the transaction because executing the script involves editing it, and if we die, we'll leave
230 | // the tx half broken (also it's not so thread safe to work on it directly.
231 | var oldTx = txContainingThis;
232 | txContainingThis = new Transaction.empty();
233 | txContainingThis.bitcoinDeserialize(new bytes.Reader(oldTx.bitcoinSerializedBytes(0)), 0);
234 | if (this.program.length > 10000 || scriptPubKey.program.length > 10000)
235 | throw new ScriptException("Script larger than 10,000 bytes");
236 |
237 | DoubleLinkedQueue stack = new DoubleLinkedQueue();
238 | DoubleLinkedQueue p2shStack = null;
239 |
240 | ScriptExecutor.executeScript(this, stack, txContainingThis, scriptSigIndex);
241 | if (enforceP2SH) p2shStack = new DoubleLinkedQueue.from(stack);
242 | ScriptExecutor.executeScript(scriptPubKey, stack, txContainingThis, scriptSigIndex);
243 |
244 | if (stack.length == 0) throw new ScriptException("Stack empty at end of script execution.");
245 |
246 | Uint8List last = stack.removeLast();
247 | if (!ScriptExecutor.castToBool(last))
248 | throw new ScriptException("Script resulted in a non-true stack: " + _printStack(stack, last));
249 |
250 | // P2SH is pay to script hash. It means that the scriptPubKey has a special form which is a valid
251 | // program but it has "useless" form that if evaluated as a normal program always returns true.
252 | // Instead, miners recognize it as special based on its template - it provides a hash of the real scriptPubKey
253 | // and that must be provided by the input. The goal of this bizarre arrangement is twofold:
254 | //
255 | // (1) You can sum up a large, complex script (like a CHECKMULTISIG script) with an address that's the same
256 | // size as a regular address. This means it doesn't overload scannable QR codes/NFC tags or become
257 | // un-wieldy to copy/paste.
258 | // (2) It allows the working set to be smaller: nodes perform best when they can store as many unspent outputs
259 | // in RAM as possible, so if the outputs are made smaller and the inputs get bigger, then it's better for
260 | // overall scalability and performance.
261 |
262 | // TODO [bitcoinj]: Check if we can take out enforceP2SH if there's a checkpoint at the enforcement block.
263 | if (enforceP2SH && PayToScriptHashOutputScript.matchesType(scriptPubKey)) {
264 | for (ScriptChunk chunk in chunks)
265 | if (chunk.isOpCode && (chunk.data[0] & 0xff) > ScriptOpCodes.OP_16)
266 | throw new ScriptException(
267 | "Attempted to spend a P2SH scriptPubKey with a script that contained script ops");
268 |
269 | Uint8List scriptPubKeyBytes = p2shStack.removeLast();
270 | Script scriptPubKeyP2SH = new Script(scriptPubKeyBytes);
271 |
272 | ScriptExecutor.executeScript(scriptPubKeyP2SH, p2shStack, txContainingThis, scriptSigIndex);
273 |
274 | if (p2shStack.length == 0)
275 | throw new ScriptException("P2SH stack empty at end of script execution.");
276 |
277 | if (!ScriptExecutor.castToBool(p2shStack.removeLast()))
278 | throw new ScriptException("P2SH script execution resulted in a non-true stack");
279 | }
280 | }
281 |
282 | String _printStack(DoubleLinkedQueue stack, [Uint8List last]) {
283 | StringBuffer sb = new StringBuffer()..write("[");
284 | for (Uint8List elem in stack) {
285 | sb..write("<" + CryptoUtils.bytesToHex(elem) + ">")..write(" ");
286 | }
287 | if (last != null) {
288 | sb.write("<" + CryptoUtils.bytesToHex(last) + ">");
289 | }
290 | return (sb..write("]")).toString();
291 | }
292 | }
293 |
--------------------------------------------------------------------------------