├── 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 | --------------------------------------------------------------------------------