3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

nemAdvent Calendar 2021

Day 23

DartでSymbolブロックチェーンのトランザクションを送信する

Last updated at Posted at 2022-06-17

この記事は「自分の得意なプログラミング言語でSymbolブロックチェーンを動かす方法」をDartで実践したものです。

pubspec.yaml

name: hello
environment:
  sdk: '>=2.10.0 <3.0.0'
dependencies:
  convert: ^3.0.2
  ed25519_edwards: ^0.3.1
  base32: 2.1.3
  sha3: ^0.2.0
  http: ^0.13.4
  hash: ^1.0.4

import

import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:hash/hash.dart';
import 'package:sha3/sha3.dart';
import 'package:base32/base32.dart';
import 'package:convert/convert.dart';
import 'package:http/http.dart' as http;
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;

アカウント作成

	var keyPair = ed.generateKey();
	var privateKey = keyPair.privateKey;
	var publicKey = keyPair.publicKey;
	print(hex.encode(privateKey.bytes));
	print(hex.encode(publicKey.bytes));

アカウント復元

	var alicePrivateKey = ed.newKeyFromSeed(hex.decode("94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7"));
	print(alicePrivateKey.bytes);

	var alicePublicKey = ed.public(alicePrivateKey);
	var aliceKeypair = ed.KeyPair(alicePrivateKey,alicePublicKey);

	print(hex.encode(alicePrivateKey.bytes));
	print(hex.encode(alicePublicKey.bytes));

	var addressHasher = SHA3(256, SHA3_PADDING, 256);
	var publicKeyHash = addressHasher.update(alicePublicKey.bytes).digest();
	var addressBody = RIPEMD160().update(publicKeyHash).digest();
	var sumHasher = SHA3(256, SHA3_PADDING, 256);
	var sumHash = sumHasher.update(hex.decode("98" + hex.encode(addressBody))).digest();
	var aliceAddress = base32.encode(hex.decode("98" + hex.encode(addressBody) + hex.encode(sumHash).substring(0,6)));
	aliceAddress = aliceAddress.substring(0, aliceAddress.length - 1);
	print(aliceAddress);

トランザクション構築

	var version = Uint8List(1)..buffer.asByteData().setInt8(0, 1);
	var networkType  = Uint8List(1)..buffer.asByteData().setInt8(0, 152);
	var transactionType   = Uint8List(2)..buffer.asByteData().setInt16(0, 16724, Endian.little);
	var fee   = Uint8List(8)..buffer.asByteData().setInt64(0, 1000000, Endian.little);
	var dt = (new DateTime.now().millisecondsSinceEpoch / 1000).floor();
	var secondLater7200 = ((dt + 7200) - 1637848847) * 1000;
	var deadline   = Uint8List(8)..buffer.asByteData().setInt64(0, secondLater7200, Endian.little);
	var recipientAddress = base32.decodeAsHexString("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
	var mosaicCount = Uint8List(1)..buffer.asByteData().setInt8(0, 1);
	var mosaicId = Uint8List(8)..buffer.asByteData().setInt64(0, 0x3A8416DB2D53B6C8, Endian.little);
	var mosaicAmount = Uint8List(8)..buffer.asByteData().setInt64(0, 100, Endian.little);
	List<int> message = utf8.encode('Hello Dart! Welcome to Symbol world!');
	var messageSize    = Uint8List(2)..buffer.asByteData().setInt16(0, message.length + 1, Endian.little);

トランザクション署名

	var verifiableBody = hex.encode(version)
		+ hex.encode(networkType)
		+ hex.encode(transactionType)
		+ hex.encode(fee)
		+ hex.encode(deadline)
		+ recipientAddress
		+ hex.encode(messageSize)
		+ hex.encode(mosaicCount)
		+ "00" + "00000000"
		+ hex.encode(mosaicId)
		+ hex.encode(mosaicAmount)
		+ "00" + hex.encode(message);

	var verifiableString = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
		+ verifiableBody;

	var verifiableBuffer = hex.decode(verifiableString);
	var signature = ed.sign(alicePrivateKey, verifiableBuffer);

トランザクションの通知

	var transactionSize = Uint8List(4)..buffer.asByteData().setInt32(0, hex.decode(verifiableBody).length + 108, Endian.little);

	var payloadString = hex.encode(transactionSize)
		+ "00000000"
		+ hex.encode(signature) 
		+ hex.encode(alicePublicKey.bytes) 
		+ "00000000" 
		+ verifiableBody;

	var payload = {
		"payload":payloadString
	};

	var response = http.put(
		Uri.parse("https://sym-test-02.opening-line.jp:3001/transactions"),
		headers: {'Content-Type': 'application/json'},
		body: jsonEncode(payload),
	)
	.then((http.Response response) {
		print("Response status: ${response.statusCode}");
		print("Response body: ${response.contentLength}");
		print(response.headers);
		print(response.request);
	});


確認


	var hashableBuffer = hex.decode(
		 hex.encode(signature) 
		+ hex.encode(alicePublicKey.bytes) 
		+ verifiableString
	);

	var hasher = SHA3(256, SHA3_PADDING, 256);
	var transactionHash = hasher.update(hashableBuffer).digest();

	print("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/" + hex.encode(transactionHash));
	print("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/" + hex.encode(transactionHash));
	print("explorer: https://testnet.symbol.fyi/transactions/" +  hex.encode(transactionHash));

検証スクリプト

import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:sha3/sha3.dart';
import 'package:hash/hash.dart';
import 'package:base32/base32.dart';
import 'package:convert/convert.dart';
import 'package:http/http.dart' as http;
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;

void main() {

	var keyPair = ed.generateKey();
	var privateKey = keyPair.privateKey;
	var publicKey = keyPair.publicKey;
	print(hex.encode(privateKey.bytes));
	print(hex.encode(publicKey.bytes));

	var alicePrivateKey = ed.newKeyFromSeed(hex.decode("94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7"));
	print(alicePrivateKey.bytes);

	var alicePublicKey = ed.public(alicePrivateKey);
	var aliceKeypair = ed.KeyPair(alicePrivateKey,alicePublicKey);

	print(hex.encode(alicePrivateKey.bytes));
	print(hex.encode(alicePublicKey.bytes));

	var addressHasher = SHA3(256, SHA3_PADDING, 256);
	var publicKeyHash = addressHasher.update(alicePublicKey.bytes).digest();
	var addressBody = RIPEMD160().update(publicKeyHash).digest();
	var sumHasher = SHA3(256, SHA3_PADDING, 256);
	var sumHash = sumHasher.update(hex.decode("98" + hex.encode(addressBody))).digest();
	var aliceAddress = base32.encode(hex.decode("98" + hex.encode(addressBody) + hex.encode(sumHash).substring(0,6)));
	aliceAddress = aliceAddress.substring(0, aliceAddress.length - 1);
	print(aliceAddress);

	var version = Uint8List(1)..buffer.asByteData().setInt8(0, 1);
	var networkType  = Uint8List(1)..buffer.asByteData().setInt8(0, 152);
	var transactionType   = Uint8List(2)..buffer.asByteData().setInt16(0, 16724, Endian.little);
	var fee   = Uint8List(8)..buffer.asByteData().setInt64(0, 1000000, Endian.little);
	var dt = (new DateTime.now().millisecondsSinceEpoch / 1000).floor();
	var secondLater7200 = ((dt + 7200) - 1637848847) * 1000;
	var deadline   = Uint8List(8)..buffer.asByteData().setInt64(0, secondLater7200, Endian.little);
	var recipientAddress = base32.decodeAsHexString("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
	var mosaicCount = Uint8List(1)..buffer.asByteData().setInt8(0, 1);
	var mosaicId = Uint8List(8)..buffer.asByteData().setInt64(0, 0x3A8416DB2D53B6C8, Endian.little);
	var mosaicAmount = Uint8List(8)..buffer.asByteData().setInt64(0, 100, Endian.little);
	List<int> message = utf8.encode('Hello Dart! Welcome to Symbol world!');
	var messageSize    = Uint8List(2)..buffer.asByteData().setInt16(0, message.length + 1, Endian.little);

	var verifiableBody = hex.encode(version)
		+ hex.encode(networkType)
		+ hex.encode(transactionType)
		+ hex.encode(fee)
		+ hex.encode(deadline)
		+ recipientAddress
		+ hex.encode(messageSize)
		+ hex.encode(mosaicCount)
		+ "00" + "00000000"
		+ hex.encode(mosaicId)
		+ hex.encode(mosaicAmount)
		+ "00" + hex.encode(message);

	var verifiableString = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
		+ verifiableBody;

	var verifiableBuffer = hex.decode(verifiableString);
	var signature = ed.sign(alicePrivateKey, verifiableBuffer);

	var transactionSize = Uint8List(4)..buffer.asByteData().setInt32(0, hex.decode(verifiableBody).length + 108, Endian.little);

	var payloadString = hex.encode(transactionSize)
		+ "00000000"
		+ hex.encode(signature) 
		+ hex.encode(alicePublicKey.bytes) 
		+ "00000000" 
		+ verifiableBody;

	var payload = {
		"payload":payloadString
	};

	var response = http.put(
		Uri.parse("https://sym-test-02.opening-line.jp:3001/transactions"),
		headers: {'Content-Type': 'application/json'},
		body: jsonEncode(payload),
	)
	.then((http.Response response) {
		print("Response status: ${response.statusCode}");
		print("Response body: ${response.contentLength}");
		print(response.headers);
		print(response.request);
	});


	var hashableBuffer = hex.decode(
		 hex.encode(signature) 
		+ hex.encode(alicePublicKey.bytes) 
		+ verifiableString
	);

	var hasher = SHA3(256, SHA3_PADDING, 256);
	var transactionHash = hasher.update(hashableBuffer).digest();

	print("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/" + hex.encode(transactionHash));
	print("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/" + hex.encode(transactionHash));
	print("explorer: https://testnet.symbol.fyi/transactions/" +  hex.encode(transactionHash));
}

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?