From 1ac0c64ed03864affb29a5da303a28a07fb9399f Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Fri, 25 Sep 2020 22:51:01 +0200
Subject: [PATCH] [feat] oxyde transaction parsing and coherence checks

---
 Cargo.lock                                    | 413 +++++++++++++++++-
 Cargo.toml                                    |   3 +
 app/lib/common-libs/constants.ts              |   2 +-
 app/lib/dto/TransactionDTO.ts                 |  31 +-
 app/lib/indexer.ts                            |  46 +-
 app/lib/rules/global_rules.ts                 | 130 +++---
 app/lib/rules/local_rules.ts                  |   6 +-
 app/service/TransactionsService.ts            |   5 +-
 neon/lib/index.ts                             |  13 +-
 neon/native/Cargo.toml                        |  11 +-
 neon/native/index.d.ts                        |   7 +
 neon/native/src/crypto.rs                     |   6 +-
 neon/native/src/lib.rs                        |  26 ++
 neon/native/src/transaction.rs                | 160 +++++++
 neon/native/transaction.d.ts                  |  26 ++
 package-lock.json                             |   5 +
 package.json                                  |   1 +
 server.ts                                     |  19 +-
 test/integration/documents-currency.ts        |  10 +-
 .../transactions/transaction-crosschain.ts    |   1 +
 ...ransaction-unlock-pubkey-with-leading-1.ts |  76 ++++
 test/neon/test_transaction.ts                 |  77 ++++
 test/unit-tools.ts                            |   4 +-
 23 files changed, 971 insertions(+), 107 deletions(-)
 create mode 100644 neon/native/src/transaction.rs
 create mode 100644 neon/native/transaction.d.ts
 create mode 100644 test/integration/transactions/transaction-unlock-pubkey-with-leading-1.ts
 create mode 100644 test/neon/test_transaction.ts

diff --git a/Cargo.lock b/Cargo.lock
index b0d819cb3..013c04009 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,14 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "addr2line"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
+dependencies = [
+ "gimli",
+]
+
 [[package]]
 name = "adler"
 version = "0.2.3"
@@ -21,12 +30,32 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
 
+[[package]]
+name = "backtrace"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "base64"
 version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
 
+[[package]]
+name = "beef"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43"
+
 [[package]]
 name = "bincode"
 version = "1.2.1"
@@ -37,6 +66,27 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
 [[package]]
 name = "bs58"
 version = "0.3.1"
@@ -49,6 +99,12 @@ version = "3.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187"
 
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
 [[package]]
 name = "byteorder"
 version = "1.3.4"
@@ -123,30 +179,97 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "cryptoxide"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da24927b5b899890bcb29205436c957b7892ec3a3fbffce81d710b9611e77778"
+
 [[package]]
 name = "cslice"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40"
 
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "dubp-common"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b41d722bf752e5f87d07685aba6df25f69881f9aafa7060fae459e2948d8080"
 dependencies = [
- "dup-crypto",
+ "dup-crypto 0.18.0",
  "serde",
  "serde_json",
  "thiserror",
 ]
 
+[[package]]
+name = "dubp-common"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fbf84c220b013b9ebd30315fb89ce1ce74ad415ca24401fbc2bd264aaa5097"
+dependencies = [
+ "dup-crypto 0.25.0",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-documents"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db28e6dd002d47cb916bc72c0ef02e2daedeb019e2710716fa5c30851fabbbca"
+dependencies = [
+ "beef",
+ "dubp-wallet",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-documents-parser"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf34add60e4c075265ef74bebebd3fd665f73eaec36133e8bb2fe8aef968a9fc"
+dependencies = [
+ "dubp-documents",
+ "json-pest-parser",
+ "pest",
+ "pest_derive",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "dubp-wallet"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe42d477e61d1f94ce759f1936b9542a7e96acd4de68002289a5fc97c9dc5f36"
+dependencies = [
+ "dubp-common 0.25.0",
+ "serde",
+ "smallvec",
+ "thiserror",
+]
+
 [[package]]
 name = "dubp-wot"
 version = "0.11.0"
 dependencies = [
  "bincode",
- "dubp-common",
+ "dubp-common 0.2.0",
  "log",
  "rayon",
  "serde",
@@ -154,15 +277,20 @@ dependencies = [
 
 [[package]]
 name = "duniteroxyde"
-version = "0.2.9"
+version = "0.3.0"
 dependencies = [
  "bincode",
  "bs58",
- "dubp-common",
+ "dubp-common 0.25.0",
+ "dubp-documents",
+ "dubp-documents-parser",
+ "dubp-wallet",
  "dubp-wot",
  "flate2",
  "neon",
  "neon-build",
+ "neon-serde",
+ "unwrap",
 ]
 
 [[package]]
@@ -181,12 +309,45 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "dup-crypto"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287b23c4281016278b47b80baf67186ce756dde738070cc7a41297da5e81dec1"
+dependencies = [
+ "base64",
+ "bs58",
+ "byteorder",
+ "cryptoxide",
+ "getrandom",
+ "ring",
+ "serde",
+ "thiserror",
+ "zeroize",
+]
+
 [[package]]
 name = "either"
 version = "1.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
 
+[[package]]
+name = "error-chain"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
+dependencies = [
+ "backtrace",
+ "version_check",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
 [[package]]
 name = "flate2"
 version = "1.0.17"
@@ -199,6 +360,33 @@ dependencies = [
  "miniz_oxide",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gimli"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.12"
@@ -223,6 +411,18 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "json-pest-parser"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bc5c84a2bceeda1ce3bd58497bde2d8cba61ca0b45873ef502401f0ff2ae8ed"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "thiserror",
+ "unwrap",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -244,6 +444,12 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
 [[package]]
 name = "maybe-uninit"
 version = "2.0.0"
@@ -306,6 +512,19 @@ dependencies = [
  "neon-sys",
 ]
 
+[[package]]
+name = "neon-serde"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f134307714cdd478581fd3cc990202ead78744d676d8437bdff64260395eb8e"
+dependencies = [
+ "error-chain",
+ "neon",
+ "neon-runtime",
+ "num",
+ "serde",
+]
+
 [[package]]
 name = "neon-sys"
 version = "0.4.0"
@@ -316,6 +535,70 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -326,6 +609,67 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "object"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
+
+[[package]]
+name = "once_cell"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.19"
@@ -388,19 +732,25 @@ checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
 
 [[package]]
 name = "ring"
-version = "0.16.12"
+version = "0.16.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
+checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
 dependencies = [
  "cc",
- "lazy_static",
  "libc",
+ "once_cell",
  "spin",
  "untrusted",
  "web-sys",
  "winapi",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
+
 [[package]]
 name = "ryu"
 version = "1.0.5"
@@ -459,6 +809,27 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "spin"
 version = "0.5.2"
@@ -517,6 +888,18 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.0"
@@ -525,9 +908,9 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
 
 [[package]]
 name = "untrusted"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
 name = "unwrap"
@@ -535,6 +918,18 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f"
 
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.60"
diff --git a/Cargo.toml b/Cargo.toml
index 9b669f412..56e722261 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,6 @@ members = [
 
 [patch.crates-io]
 #dubp-common = { path = "../dubp-rs-libs/common" }
+#dubp-documents  = { path = "../dubp-rs-libs/documents" }
+#dubp-documents-parser = { path = "../dubp-rs-libs/documents-parser" }
+#dubp-wallet  = { path = "../dubp-rs-libs/wallet" }
diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts
index a0b6151cf..3c501ab4c 100755
--- a/app/lib/common-libs/constants.ts
+++ b/app/lib/common-libs/constants.ts
@@ -23,7 +23,7 @@ const SIGNATURE = "[A-Za-z0-9+\\/=]{87,88}";
 const USER_ID = "[A-Za-z0-9_-]{2,100}";
 const INTEGER = "(0|[1-9]\\d{0,18})";
 const FINGERPRINT = "[A-F0-9]{64}";
-const BLOCK_VERSION = "(10|11|12)";
+const BLOCK_VERSION = "(1[0-3])";
 const TX_VERSION = "(10)";
 const DIVIDEND = "[1-9][0-9]{0,5}";
 const ZERO_OR_POSITIVE_INT = "0|[1-9][0-9]{0,18}";
diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts
index 71dfa660f..6cd8bf15a 100644
--- a/app/lib/dto/TransactionDTO.ts
+++ b/app/lib/dto/TransactionDTO.ts
@@ -14,6 +14,7 @@
 import { hashf } from "../common";
 import { Cloneable } from "./Cloneable";
 import { verify } from "../../../neon/lib";
+import { TransactionDTOV10 } from "../../../neon/native";
 
 export interface BaseDTO {
   base: number;
@@ -252,7 +253,7 @@ export class TransactionDTO implements Cloneable {
     };
   }
 
-  getTransactionSigResult(dubp_version: number) {
+  getTransactionSigResult() {
     const sigResult = new TxSignatureResultImpl(this.issuers.slice());
     let i = 0;
     const raw = this.getRawTxNoSig();
@@ -260,20 +261,15 @@ export class TransactionDTO implements Cloneable {
     while (matching && i < this.signatures.length) {
       const sig = this.signatures[i];
       const pub = this.issuers[i];
-      if (dubp_version >= 12) {
-        sigResult.sigs[i].ok = verify(raw, sig, pub);
-      } else {
-        // TODO ESZ list all invalid transactions
-        sigResult.sigs[i].ok = verify(raw, sig, pub);
-      }
+      sigResult.sigs[i].ok = verify(raw, sig, pub);
       matching = sigResult.sigs[i].ok;
       i++;
     }
     return sigResult;
   }
 
-  checkSignatures(dubp_version: number) {
-    return this.getTransactionSigResult(dubp_version).allMatching;
+  checkSignatures() {
+    return this.getTransactionSigResult().allMatching;
   }
 
   static fromJSONObject(obj: any, currency: string = "") {
@@ -293,6 +289,23 @@ export class TransactionDTO implements Cloneable {
     );
   }
 
+  static fromTransactionDTOV10(txV10: TransactionDTOV10) {
+    return new TransactionDTO(
+      10,
+      txV10.currency || "",
+      txV10.locktime || 0,
+      txV10.hash || "",
+      txV10.blockstamp || "",
+      txV10.blockstampTime || 0,
+      txV10.issuers || [],
+      txV10.inputs || [],
+      txV10.outputs || [],
+      txV10.unlocks || [],
+      txV10.signatures || [],
+      txV10.comment || ""
+    );
+  }
+
   static toRAW(json: TransactionDTO, noSig = false) {
     let raw = "";
     raw += "Version: " + json.version + "\n";
diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts
index c691672f4..c87fe09f7 100644
--- a/app/lib/indexer.ts
+++ b/app/lib/indexer.ts
@@ -18,7 +18,7 @@ import { RevocationDTO } from "./dto/RevocationDTO";
 import { CertificationDTO } from "./dto/CertificationDTO";
 import { TransactionDTO } from "./dto/TransactionDTO";
 import { DBHead } from "./db/DBHead";
-import { verify } from "../../neon/lib";
+import { sourceIsUnlockable, verify } from "../../neon/lib";
 import { rawer, txunlock } from "./common-libs/index";
 import { CommonConstants } from "./common-libs/constants";
 import { MembershipDTO } from "./dto/MembershipDTO";
@@ -2634,22 +2634,38 @@ function txSourceUnlock(
   source: { conditions: string; written_time: number },
   HEAD: DBHead
 ) {
-  const tx = ENTRY.txObj;
-  const unlockParams: string[] = TransactionDTO.unlock2params(
-    ENTRY.unlock || ""
-  );
-  const unlocksMetadata: UnlockMetadata = {};
-  const sigResult = TransactionDTO.fromJSONObject(tx).getTransactionSigResult(
-    HEAD.version
-  );
   if (!source.conditions) {
     return false; // Unlock fail
   }
-  if (source.conditions.match(/CLTV/)) {
-    unlocksMetadata.currentTime = HEAD.medianTime;
-  }
-  if (source.conditions.match(/CSV/)) {
-    unlocksMetadata.elapsedTime = HEAD.medianTime - source.written_time;
+  const tx = ENTRY.txObj;
+  if (HEAD.version >= 13) {
+    // Vérifier si les proofs d'une source (en input d'une tx) sont valides ou non
+    return sourceIsUnlockable(
+      HEAD.medianTime,
+      tx.issuers,
+      ENTRY.unlock || "",
+      source.written_time,
+      source.conditions
+    );
+  } else {
+    const unlockParams: string[] = TransactionDTO.unlock2params(
+      ENTRY.unlock || ""
+    );
+    const unlocksMetadata: UnlockMetadata = {};
+    const sigResult = TransactionDTO.fromJSONObject(
+      tx
+    ).getTransactionSigResult();
+    if (source.conditions.match(/CLTV/)) {
+      unlocksMetadata.currentTime = HEAD.medianTime;
+    }
+    if (source.conditions.match(/CSV/)) {
+      unlocksMetadata.elapsedTime = HEAD.medianTime - source.written_time;
+    }
+    return txunlock(
+      source.conditions,
+      unlockParams,
+      sigResult,
+      unlocksMetadata
+    );
   }
-  return txunlock(source.conditions, unlockParams, sigResult, unlocksMetadata);
 }
diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts
index c168807bf..eeb03fb05 100644
--- a/app/lib/rules/global_rules.ts
+++ b/app/lib/rules/global_rules.ts
@@ -16,7 +16,7 @@ import { FileDAL } from "../dal/fileDAL";
 import { DBBlock } from "../db/DBBlock";
 import { TransactionDTO, TxSignatureResult } from "../dto/TransactionDTO";
 import { BlockDTO } from "../dto/BlockDTO";
-import { verify } from "../../../neon/lib";
+import { txsInputsAreUnlockable, verify } from "../../../neon/lib";
 import { rawer, txunlock } from "../common-libs/index";
 import { CommonConstants } from "../common-libs/constants";
 import { IdentityDTO } from "../dto/IdentityDTO";
@@ -129,29 +129,31 @@ export const GLOBAL_RULES_FUNCTIONS = {
         let index = parseInt(sp[0]);
         unlocks[index] = sp[1];
       }
-      for (let k = 0, len2 = inputs.length; k < len2; k++) {
-        let src = inputs[k];
-        let dbSrc: SimpleTxInput | null = await dal.getSource(
-          src.identifier,
-          src.pos,
-          src.type === "D"
+      let k = 0; // need  for DUBP version <= 12
+      let sources_conditions = []; // need  for DUBP version >= 13
+      let sources_written_on = []; // need  for DUBP version >= 13
+      for (const input of inputs) {
+        let source: SimpleTxInput | null = await dal.getSource(
+          input.identifier,
+          input.pos,
+          input.type === "D"
         );
         logger.debug(
           "Source %s:%s:%s:%s = %s",
-          src.amount,
-          src.base,
-          src.identifier,
-          src.pos,
-          dbSrc && dbSrc.consumed
+          input.amount,
+          input.base,
+          input.identifier,
+          input.pos,
+          source && source.consumed
         );
-        if (!dbSrc) {
+        if (!source) {
           // For chained transactions which are checked on sandbox submission, we accept them if there is already
           // a previous transaction of the chain already recorded in the pool
-          dbSrc = await (async () => {
+          source = await (async () => {
             let hypotheticSrc: any = null;
-            let targetTX = await findSourceTx(src.identifier);
+            let targetTX = await findSourceTx(input.identifier);
             if (targetTX) {
-              let outputStr = targetTX.outputs[src.pos];
+              let outputStr = targetTX.outputs[input.pos];
               if (outputStr) {
                 hypotheticSrc = TransactionDTO.outputStr2Obj(outputStr);
                 hypotheticSrc.consumed = false;
@@ -161,55 +163,77 @@ export const GLOBAL_RULES_FUNCTIONS = {
             return hypotheticSrc;
           })();
         }
-        if (!dbSrc || dbSrc.consumed) {
+        if (!source || source.consumed) {
           logger.warn(
             "Source " +
-              [src.type, src.identifier, src.pos].join(":") +
+              [input.type, input.identifier, input.pos].join(":") +
               " is not available"
           );
           throw constants.ERRORS.SOURCE_ALREADY_CONSUMED;
         }
-        sumOfInputs += dbSrc.amount * Math.pow(10, dbSrc.base);
-        if (block.medianTime - dbSrc.written_time < tx.locktime) {
+        sumOfInputs += source.amount * Math.pow(10, source.base);
+        if (block.medianTime - source.written_time < tx.locktime) {
           throw constants.ERRORS.LOCKTIME_PREVENT;
         }
-        let unlockValues = unlocks[k];
-        let unlocksForCondition: string[] = (unlockValues || "").split(" ");
-        let unlocksMetadata: any = {};
-        if (dbSrc.conditions) {
-          if (dbSrc.conditions.match(/CLTV/)) {
-            unlocksMetadata.currentTime = block.medianTime;
-          }
-
-          if (dbSrc.conditions.match(/CSV/)) {
-            unlocksMetadata.elapsedTime = block.medianTime - dbSrc.written_time;
-          }
+        if (block.version <= 12) {
+          const sigs = tx.getTransactionSigResult();
+          let unlockValues = unlocks[k];
+          k++;
+          let unlocksForCondition: string[] = (unlockValues || "").split(" ");
+          let unlocksMetadata: any = {};
+          if (source.conditions) {
+            if (source.conditions.match(/CLTV/)) {
+              unlocksMetadata.currentTime = block.medianTime;
+            }
 
-          const sigs = tx.getTransactionSigResult(block.version);
+            if (source.conditions.match(/CSV/)) {
+              unlocksMetadata.elapsedTime =
+                block.medianTime - source.written_time;
+            }
 
-          try {
-            if (
-              !txunlock(
-                dbSrc.conditions,
-                unlocksForCondition,
-                sigs,
-                unlocksMetadata
-              )
-            ) {
-              throw Error("Locked");
+            try {
+              if (
+                !txunlock(
+                  source.conditions,
+                  unlocksForCondition,
+                  sigs,
+                  unlocksMetadata
+                )
+              ) {
+                throw Error("Locked");
+              }
+            } catch (e) {
+              logger.warn(
+                "Source " +
+                  [
+                    input.amount,
+                    input.base,
+                    input.type,
+                    input.identifier,
+                    input.pos,
+                  ].join(":") +
+                  " unlock fail"
+              );
+              throw constants.ERRORS.WRONG_UNLOCKER;
             }
-          } catch (e) {
-            logger.warn(
-              "Source " +
-                [src.amount, src.base, src.type, src.identifier, src.pos].join(
-                  ":"
-                ) +
-                " unlock fail"
-            );
-            throw constants.ERRORS.WRONG_UNLOCKER;
+          } else {
+            throw Error("Source with no conditions");
           }
-        } else {
-          throw Error("Source with no conditions");
+        }
+        sources_conditions.push(source.conditions);
+        sources_written_on.push(source.written_time);
+      }
+      if (block.version >= 13) {
+        // No need to check the validity of the signatures, this is already done by local verification.
+        if (
+          !txsInputsAreUnlockable(
+            block.medianTime,
+            sources_conditions,
+            sources_written_on,
+            tx
+          )
+        ) {
+          throw constants.ERRORS.WRONG_UNLOCKER;
         }
       }
       let sumOfOutputs = outputs.reduce(function (p, output) {
diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts
index ea5f8a578..feaeefc3b 100644
--- a/app/lib/rules/local_rules.ts
+++ b/app/lib/rules/local_rules.ts
@@ -522,7 +522,7 @@ export const LOCAL_RULES_FUNCTIONS = {
     const txs = block.transactions;
     // Check rule against each transaction
     for (const tx of txs) {
-      if (!tx.checkSignatures(block.version)) {
+      if (!tx.checkSignatures()) {
         throw Error("Signature from a transaction must match");
       }
     }
@@ -625,6 +625,7 @@ function checkBunchOfTransactions(
     medianTime,
   };
   const index = Indexer.localIndex(block, conf);
+
   return (async () => {
     let local_rule = LOCAL_RULES_FUNCTIONS;
     await local_rule.checkTxLen(block);
@@ -650,9 +651,6 @@ export const LOCAL_RULES_HELPERS = {
 
   getMaxTransactionDepth,
 
-  checkSingleTransactionLocally: (tx: any, conf: ConfDTO) =>
-    checkBunchOfTransactions([tx], conf, 0),
-
   checkTxAmountsValidity: (tx: TransactionDTO) => {
     const inputs = tx.inputsAsObjects();
     const outputs = tx.outputsAsObjects();
diff --git a/app/service/TransactionsService.ts b/app/service/TransactionsService.ts
index ec67cb29b..e7cbf8cc3 100644
--- a/app/service/TransactionsService.ts
+++ b/app/service/TransactionsService.ts
@@ -38,8 +38,8 @@ export class TransactionService extends FIFOService {
     this.logger = require("../lib/logger").NewLogger(this.dal.profile);
   }
 
-  processTx(txObj: any) {
-    const tx = TransactionDTO.fromJSONObject(txObj, this.conf.currency);
+  // Called only when receiving a doc tx via BMA or WS2P
+  processVerifiedTx(tx: TransactionDTO) {
     const hash = tx.getHash();
     return this.pushFIFO<TransactionDTO>(hash, async () => {
       try {
@@ -62,7 +62,6 @@ export class TransactionService extends FIFOService {
         // Start checks...
         const fakeTimeVariation = current.medianTime + 1;
         const dto = TransactionDTO.fromJSONObject(tx);
-        await LOCAL_RULES_HELPERS.checkSingleTransactionLocally(dto, this.conf);
         await GLOBAL_RULES_HELPERS.checkTxBlockStamp(tx, this.dal);
         await GLOBAL_RULES_HELPERS.checkSingleTransaction(
           dto,
diff --git a/neon/lib/index.ts b/neon/lib/index.ts
index 48fdad95c..f24c20e15 100644
--- a/neon/lib/index.ts
+++ b/neon/lib/index.ts
@@ -1,3 +1,14 @@
-export { Ed25519Signator, generateRandomSeed, seedToSecretKey, sha256, verify, Wot } from "../native";
+export {
+    Ed25519Signator,
+    generateRandomSeed,
+    rawTxParseAndVerify,
+    sha256,
+    seedToSecretKey,
+    sourceIsUnlockable,
+    txVerify,
+    txsInputsAreUnlockable,
+    verify,
+    Wot
+} from "../native";
 export { KeyPairBuilder } from "./crypto";
 export { WotBuilder } from "./wot";
diff --git a/neon/native/Cargo.toml b/neon/native/Cargo.toml
index 363e8b702..487c7d822 100644
--- a/neon/native/Cargo.toml
+++ b/neon/native/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "duniteroxyde"
-version = "0.2.9"
+version = "0.3.0"
 authors = ["librelois <elois@ifee.fr>"]
 license = "AGPL-3.0"
 build = "build.rs"
@@ -17,7 +17,14 @@ neon-build = "0.4.0"
 [dependencies]
 bincode = "1.2.1"
 bs58 = "0.3.0"
-dubp-common = { version = "0.2.0", features = ["rand", "scrypt"] }
+dubp-common = { version = "0.25.0", features = ["crypto_scrypt"] }
+dubp-documents = { version = "0.25.0" }
+dubp-documents-parser = { version = "0.25.0" }
+dubp-wallet = { version = "0.25.0" }
 dubp-wot = { path = "../../rust-libs/dubp-wot" }
 flate2 = "1.0.16"
 neon = "0.4.0"
+neon-serde = "0.4.0"
+
+[dev-dependencies]
+unwrap = "1.2.1"
diff --git a/neon/native/index.d.ts b/neon/native/index.d.ts
index e31c8cbdf..abec08383 100644
--- a/neon/native/index.d.ts
+++ b/neon/native/index.d.ts
@@ -1,6 +1,7 @@
 /* tslint:disable */
 
 import * as _crypto from './crypto';
+import * as _transactions from './transaction';
 import * as _wot from './wot';
 
 export import Ed25519Signator = _crypto.Ed25519Signator;
@@ -9,5 +10,11 @@ export import seedToSecretKey = _crypto.seedToSecretKey;
 export import sha256 = _crypto.sha256;
 export import verify = _crypto.verify;
 
+export import TransactionDTOV10 = _transactions.TransactionDTOV10;
+export import rawTxParseAndVerify = _transactions.rawTxParseAndVerify;
+export import sourceIsUnlockable = _transactions.sourceIsUnlockable;
+export import txVerify = _transactions.txVerify;
+export import txsInputsAreUnlockable = _transactions.txsInputsAreUnlockable;
+
 export import Wot = _wot.Wot;
 export import DetailedDistance = _wot.DetailedDistance;
diff --git a/neon/native/src/crypto.rs b/neon/native/src/crypto.rs
index bd42f741e..c7399976c 100644
--- a/neon/native/src/crypto.rs
+++ b/neon/native/src/crypto.rs
@@ -194,13 +194,15 @@ fn sign_bytes<'c>(
 mod tests {
 
     use super::*;
+    use unwrap::unwrap;
 
     #[test]
     fn test_keypair_from_expanded_base58_secret_key() {
         let expanded_base58_secret_key = "51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP";
 
-        let signator = keypair_from_expanded_base58_secret_key(expanded_base58_secret_key)
-            .expect("fail to generate keypair");
+        let signator = unwrap!(keypair_from_expanded_base58_secret_key(
+            expanded_base58_secret_key
+        ));
 
         assert_eq!(
             "HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd",
diff --git a/neon/native/src/lib.rs b/neon/native/src/lib.rs
index 94aa1964d..bc28e4cd3 100644
--- a/neon/native/src/lib.rs
+++ b/neon/native/src/lib.rs
@@ -13,7 +13,20 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+#![deny(
+    clippy::expect_used,
+    clippy::unwrap_used,
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unsafe_code,
+    unstable_features,
+    unused_import_braces
+)]
+
 mod crypto;
+mod transaction;
 mod wot;
 
 use neon::{prelude::*, register_module};
@@ -37,6 +50,19 @@ register_module!(mut cx, {
     cx.export_function("sha256", crate::crypto::sha256)?;
     cx.export_function("verify", crate::crypto::verify)?;
     cx.export_class::<crate::crypto::JsKeyPair>("Ed25519Signator")?;
+    cx.export_function(
+        "rawTxParseAndVerify",
+        crate::transaction::raw_tx_parse_and_verify,
+    )?;
+    cx.export_function(
+        "sourceIsUnlockable",
+        crate::transaction::source_is_unlockable,
+    )?;
+    cx.export_function(
+        "txsInputsAreUnlockable",
+        crate::transaction::txs_inputs_are_unlockable,
+    )?;
+    cx.export_function("txVerify", crate::transaction::tx_verify)?;
     cx.export_class::<crate::wot::JsWoT>("Wot")?;
     Ok(())
 });
diff --git a/neon/native/src/transaction.rs b/neon/native/src/transaction.rs
new file mode 100644
index 000000000..a7dcba096
--- /dev/null
+++ b/neon/native/src/transaction.rs
@@ -0,0 +1,160 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::into_neon_res;
+use dubp_common::crypto::{bases::BaseConversionError, keys::ed25519, keys::PublicKey};
+use dubp_documents::transaction::{
+    TransactionDocumentTrait, TransactionDocumentV10, TransactionDocumentV10Stringified,
+    TransactionInputUnlocksV10,
+};
+use dubp_documents::{prelude::*, smallvec::SmallVec};
+use dubp_documents_parser::prelude::*;
+use dubp_wallet::prelude::*;
+use neon::prelude::*;
+
+pub fn raw_tx_parse_and_verify(mut cx: FunctionContext) -> JsResult<JsValue> {
+    let raw_tx = cx.argument::<JsString>(0)?.value();
+    let currency_opt = if let Some(arg1) = cx.argument_opt(1) {
+        Some(arg1.downcast_or_throw::<JsString, _>(&mut cx)?.value())
+    } else {
+        None
+    };
+
+    match TransactionDocumentV10::parse_from_raw_text(&raw_tx) {
+        Ok(tx) => {
+            if let Err(e) = tx.verify(currency_opt) {
+                cx.throw_error(format!("{}", e))
+            } else {
+                let tx_stringified = tx.to_string_object();
+                Ok(neon_serde::to_value(&mut cx, &tx_stringified)?)
+            }
+        }
+        Err(e) => cx.throw_error(format!("{}", e)),
+    }
+}
+
+pub fn tx_verify(mut cx: FunctionContext) -> JsResult<JsUndefined> {
+    let tx_obj = cx.argument::<JsValue>(0)?;
+    let currency_opt = if let Some(arg1) = cx.argument_opt(1) {
+        Some(arg1.downcast_or_throw::<JsString, _>(&mut cx)?.value())
+    } else {
+        None
+    };
+
+    let tx_stringified: TransactionDocumentV10Stringified =
+        neon_serde::from_value(&mut cx, tx_obj)?;
+    match TransactionDocumentV10::from_string_object(&tx_stringified) {
+        Ok(tx) => {
+            if let Err(e) = tx.verify(currency_opt) {
+                cx.throw_error(format!("{}", e))
+            } else {
+                Ok(cx.undefined())
+            }
+        }
+        Err(e) => cx.throw_error(format!("{}", e)),
+    }
+}
+
+pub fn txs_inputs_are_unlockable(mut cx: FunctionContext) -> JsResult<JsBoolean> {
+    let current_bc_time = cx.argument::<JsNumber>(0)?.value() as u64;
+    let inputs_scripts_js = cx.argument::<JsValue>(1)?;
+    let inputs_written_on_js = cx.argument::<JsValue>(2)?;
+    let tx_obj = cx.argument::<JsValue>(3)?;
+
+    let inputs_scripts_str: Vec<String> = neon_serde::from_value(&mut cx, inputs_scripts_js)?;
+    let inputs_written_on: Vec<u64> = neon_serde::from_value(&mut cx, inputs_written_on_js)?;
+    let tx_stringified: TransactionDocumentV10Stringified =
+        neon_serde::from_value(&mut cx, tx_obj)?;
+
+    match TransactionDocumentV10::from_string_object(&tx_stringified) {
+        Ok(tx) => {
+            let proofs = tx.get_inputs_unlocks();
+            let tx_issuers = tx.issuers();
+            for i in 0..proofs.len() {
+                if !source_is_unlockable_inner(
+                    current_bc_time,
+                    &proofs[i],
+                    inputs_written_on[i],
+                    &tx_issuers,
+                    &inputs_scripts_str[i],
+                ) {
+                    return Ok(cx.boolean(false));
+                }
+            }
+            // All proofs are valid and **seem** useful for all inputs
+            Ok(cx.boolean(true))
+        }
+        Err(e) => {
+            // Tx malformated
+            println!("{}", e);
+            Ok(cx.boolean(false))
+        }
+    }
+}
+
+pub fn source_is_unlockable(mut cx: FunctionContext) -> JsResult<JsBoolean> {
+    let current_bc_time = cx.argument::<JsNumber>(0)?.value() as u64;
+    let tx_issuers_js = cx.argument::<JsValue>(1)?;
+    let proofs = cx.argument::<JsString>(2)?.value();
+    let source_written_on = cx.argument::<JsNumber>(3)?.value() as u64;
+    let utxo_script = cx.argument::<JsString>(4)?.value();
+
+    let tx_issuers_str: Vec<String> = neon_serde::from_value(&mut cx, tx_issuers_js)?;
+    let tx_issuers_res = tx_issuers_str
+        .iter()
+        .map(|s| ed25519::PublicKey::from_base58(s))
+        .collect::<Result<SmallVec<[ed25519::PublicKey; 1]>, BaseConversionError>>();
+    let tx_issuers = into_neon_res(&mut cx, tx_issuers_res.map_err(|e| format!("{}", e)))?;
+
+    if let Ok(proofs) = dubp_documents_parser::tx_unlock_v10_from_str(&proofs) {
+        Ok(cx.boolean(source_is_unlockable_inner(
+            current_bc_time,
+            &proofs,
+            source_written_on,
+            &tx_issuers,
+            &utxo_script,
+        )))
+    } else {
+        // Proofs malformated
+        Ok(cx.boolean(false))
+    }
+}
+
+fn source_is_unlockable_inner(
+    current_bc_time: u64,
+    proofs: &TransactionInputUnlocksV10,
+    source_written_on: u64,
+    tx_issuers: &[ed25519::PublicKey],
+    utxo_script: &str,
+) -> bool {
+    if let Ok(utxo_script) = dubp_documents_parser::wallet_script_from_str(&utxo_script) {
+        if let Ok(unlockable_on) = SourceV10::unlockable_on(
+            &tx_issuers,
+            &proofs.unlocks,
+            source_written_on,
+            &utxo_script,
+        ) {
+            // All proofs are valid and **seem** useful
+            // (it is too costly to determine the minimum set of proof that is strictly necessary and sufficient).
+            unlockable_on <= current_bc_time
+        } else {
+            // Invalid or insufficient or too much proofs (to prevent spam with a lot of useless proofs).
+            false
+        }
+    } else {
+        // Invalid source, can never be consumed
+        false
+    }
+}
diff --git a/neon/native/transaction.d.ts b/neon/native/transaction.d.ts
new file mode 100644
index 000000000..9c08fe470
--- /dev/null
+++ b/neon/native/transaction.d.ts
@@ -0,0 +1,26 @@
+/* tslint:disable */
+
+export class TransactionDTOV10 {
+    currency: string;
+    locktime: number;
+    hash: string;
+    blockstamp: string;
+    blockstampTime: number;
+    issuers: string[];
+    inputs: string[];
+    outputs: string[];
+    unlocks: string[];
+    signatures: string[];
+    comment: string;
+}
+
+export function rawTxParseAndVerify(raw: string, currency?: string): TransactionDTOV10;
+export function sourceIsUnlockable(
+    currentBcTime: number,
+    txIssuers: string[],
+    proofs: string, 
+    sourceWrittenOn: number,
+    utxoScript: string
+): boolean;
+export function txVerify(tx: TransactionDTOV10, currency?: string): void;
+export function txsInputsAreUnlockable(currentBcTime: number, inputsConditions: string[], inputsWrittenOn: number[], tx: TransactionDTOV10): boolean;
diff --git a/package-lock.json b/package-lock.json
index 5d995d3aa..1b922eef5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,11 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "10": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/10/-/10-0.0.1.tgz",
+      "integrity": "sha1-dpElm1QaWkKJ8+CfSjVrIr5M/yA="
+    },
     "@babel/code-frame": {
       "version": "7.8.3",
       "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
diff --git a/package.json b/package.json
index e19f3970d..20e2cc519 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
     "url": "https://git.duniter.org/nodes/typescript/duniter/issues"
   },
   "dependencies": {
+    "10": "0.0.1",
     "archiver": "3.1.1",
     "async": "3.2.0",
     "bindings": "1.5.0",
diff --git a/server.ts b/server.ts
index 76c600450..ee920422d 100644
--- a/server.ts
+++ b/server.ts
@@ -45,6 +45,7 @@ import {LevelUp} from "levelup";
 import {BMAConstants} from "./app/modules/bma/lib/constants"
 import {HttpMilestonePage} from "./app/modules/bma/lib/dtos"
 import * as toJson from "./app/modules/bma/lib/tojson"
+import { rawTxParseAndVerify, txVerify } from "./neon/lib"
 
 export interface HookableServer {
   generatorGetJoinData: (...args:any[]) => Promise<any>
@@ -309,12 +310,22 @@ export class Server extends stream.Duplex implements HookableServer {
   }
 
   async writeRawTransaction(raw:string): Promise<TransactionDTO> {
-    const obj = parsers.parseTransaction.syncWrite(raw)
-    return await this.writeTransaction(obj)
+    let tx = rawTxParseAndVerify(raw, this.conf.currency)
+    return await this.writeVerifiedTransaction(TransactionDTO.fromTransactionDTOV10(tx))
   }
 
-  async writeTransaction(obj:any, notify = true) {
-    const res = await this.TransactionsService.processTx(obj)
+  async writeTransaction(txObj:any, notify = true) {
+    const tx = TransactionDTO.fromJSONObject(txObj, this.conf.currency);
+    try {
+      txVerify(tx);
+      return await this.writeVerifiedTransaction(tx)
+    } catch (e) {
+      throw e;
+    }
+  }
+
+  async writeVerifiedTransaction(verifiedTx: TransactionDTO, notify = true) {
+    const res = await this.TransactionsService.processVerifiedTx(verifiedTx);
     if (notify) {
       this.emitDocument(res, DuniterDocument.ENTITY_TRANSACTION)
     }
diff --git a/test/integration/documents-currency.ts b/test/integration/documents-currency.ts
index 1aeb9edc3..c0772ac74 100644
--- a/test/integration/documents-currency.ts
+++ b/test/integration/documents-currency.ts
@@ -14,6 +14,7 @@
 
 import {NewTestingServer} from './tools/toolbox';
 import {TestUser} from "./tools/TestUser"
+import { assertEqual } from './tools/test-framework';
 
 const should    = require('should');
 
@@ -177,7 +178,7 @@ describe("Document pool currency", function() {
     } catch (e) {
       should.exist(e.error);
       e.should.be.an.Object();
-      e.error.message.should.match(/Signature from a transaction must match/);
+      e.error.message.should.match(/Wrong currency/);
     }
   })
 
@@ -202,7 +203,12 @@ describe("Document pool currency", function() {
     } catch (e) {
       should.exist(e.error);
       e.should.be.an.Object();
-      e.error.message.should.match(/Wrong output format/);
+      assertEqual(`TextDocumentParseError: Grammar error:   --> 13:77
+   |
+13 | 1500:1:XHX(6B86B273FF34FCE19D6B804EFF5A3F5747ADA4EAA22F1D49C01E52DDB7875B4B))␊
+   |                                                                             ^---
+   |
+   = expected output_cond_op_and or output_cond_op_or`, e.error.message);
     }
   })
 
diff --git a/test/integration/transactions/transaction-crosschain.ts b/test/integration/transactions/transaction-crosschain.ts
index 2ea332e92..d1fdf21ae 100644
--- a/test/integration/transactions/transaction-crosschain.ts
+++ b/test/integration/transactions/transaction-crosschain.ts
@@ -17,6 +17,7 @@ import {Underscore} from "../../../app/lib/common-libs/underscore"
 import {HttpSources} from "../../../app/modules/bma/lib/dtos"
 import {shouldFail, shouldNotFail} from "../../unit-tools"
 import {expectAnswer} from "../tools/http-expect"
+import { log } from "console"
 
 const assert = require('assert');
 const should = require('should');
diff --git a/test/integration/transactions/transaction-unlock-pubkey-with-leading-1.ts b/test/integration/transactions/transaction-unlock-pubkey-with-leading-1.ts
new file mode 100644
index 000000000..092a73df5
--- /dev/null
+++ b/test/integration/transactions/transaction-unlock-pubkey-with-leading-1.ts
@@ -0,0 +1,76 @@
+// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1
+// Copyright (C) 2018  Cedric Moreau <cem.moreau@gmail.com>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+
+import {simpleNodeWith2Users, TestingServer} from "../tools/toolbox"
+import {TestUser} from "../tools/TestUser"
+import {shouldFail, shouldNotFail} from "../../unit-tools"
+import { CommonConstants } from "../../../app/lib/common-libs/constants";
+
+const should = require('should');
+const assert = require('assert');
+
+const now = 1480000000;
+
+const conf = {
+  dt: 10,
+  ud0: 200,
+  udTime0: now - 1, // So we have a UD right on block#1
+  medianTimeBlocks: 1 // Easy: medianTime(b) = time(b-1)
+};
+
+let s1:TestingServer, cat:TestUser, tac:TestUser
+
+describe("Transactions unlock with leading 1", () => {
+
+    before(async () => {
+        const res = await simpleNodeWith2Users(conf);
+        s1 = res.s1;
+        cat = res.cat;
+        tac = res.tac;
+        CommonConstants.BLOCK_GENESIS_VERSION = 13;
+        await s1.commit({ time: now });
+        await s1.commit({ time: now + 1 });
+    })
+
+    after(() => {
+        //CommonConstants.DUBP_NEXT_VERSION = 10
+        CommonConstants.BLOCK_GENESIS_VERSION = 10;
+        return Promise.all([
+            s1.closeCluster()
+        ])
+    })
+
+    it('it should exist block#1 with UD of 200', () => s1.expect('/blockchain/block/1', (block:any) => {
+        should.exists(block);
+        assert.equal(block.number, 1);
+        assert.equal(block.dividend, 200);
+    }));
+
+    it('Send money to pubkey with leading 1 and consume without leading 1', async () => {
+        let tx1 = await cat.prepareITX(200, "1XoFs76G4yidvVY3FZBwYyLXTMjabryhFD8mNQPkQKHk");
+        await shouldNotFail(cat.sendTX(tx1));
+        await s1.commit({ time: now + 4 });
+        let current = await s1.get('/blockchain/current');
+        // Try to consume money without leading 1
+        const pub43Keyring = { pub: 'XoFs76G4yidvVY3FZBwYyLXTMjabryhFD8mNQPkQKHk', sec: '2ZuLaTHpkMkUcUJCGrAYBLHffEFCtUkNbq5UYeBQUkX9suoQzt5zcxZ6Q4B21LcHnDRUVUsu1hyfa9EzKrHFmSqx'};
+        const pub43 = new TestUser('pub43', pub43Keyring, { server: s1 });
+        let tx2 = await pub43.prepareUTX(tx1, ['SIG(0)'], [{ qty: 200, base: 0, lock: 'SIG(' + cat.pub + ')' }], {
+            blockstamp: [current.number, current.hash].join('-')
+        });
+        await shouldNotFail(pub43.sendTX(tx2));
+        await s1.commit({ time: now + 5 });
+        let tx3 = await cat.prepareITX(200, tac);
+        await shouldNotFail(cat.sendTX(tx3));
+        await s1.commit({ time: now + 15 });
+    });
+})
diff --git a/test/neon/test_transaction.ts b/test/neon/test_transaction.ts
new file mode 100644
index 000000000..ea6cd68d8
--- /dev/null
+++ b/test/neon/test_transaction.ts
@@ -0,0 +1,77 @@
+
+import { rawTxParseAndVerify, sourceIsUnlockable, txVerify, txsInputsAreUnlockable } from "../../neon/lib";
+import * as assert from "assert";
+import { TransactionDTOV10 } from "../../neon/native";
+
+let rawTx: string;
+//let txV10: TransactionDTOV10;
+
+describe('Transaction tests:', function(){
+
+    before(async () => {
+      rawTx = `Version: 10
+Type: Transaction
+Currency: duniter_unit_test_currency
+Blockstamp: 204-00003E2B8A35370BA5A7064598F628A62D4E9EC1936BE8651CE9A85F2E06981B
+Locktime: 0
+Issuers:
+DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV
+4tNQ7d9pj2Da5wUVoW9mFn7JjuPoowF977au8DdhEjVR
+FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa
+Inputs:
+40:2:T:6991C993631BED4733972ED7538E41CCC33660F554E3C51963E2A0AC4D6453D3:2
+70:2:T:3A09A20E9014110FD224889F13357BAB4EC78A72F95CA03394D8CCA2936A7435:8
+20:2:D:DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV:46
+70:2:T:A0D9B4CDC113ECE1145C5525873821398890AE842F4B318BD076095A23E70956:3
+20:2:T:67F2045B5318777CC52CD38B424F3E40DDA823FA0364625F124BABE0030E7B5B:5
+15:2:D:FD9wujR7KABw88RyKEGBYRLz8PA6jzVCbcBAsrBXBqSa:46
+Unlocks:
+0:SIG(0)
+1:XHX(7665798292)
+2:SIG(0)
+3:SIG(0) SIG(2)
+4:SIG(0) SIG(1) SIG(2)
+5:SIG(2)
+Outputs:
+120:2:SIG(BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g)
+146:2:SIG(DSz4rgncXCytsUMW2JU2yhLquZECD2XpEkpP9gG5HyAx)
+49:2:(SIG(6DyGr5LFtFmbaJYRvcs9WmBsr4cbJbJ1EV9zBbqG7A6i) || XHX(3EB4702F2AC2FD3FA4FDC46A4FC05AE8CDEE1A85F2AC2FD3FA4FDC46A4FC01CA))
+Comment: -----@@@----- (why not this comment?)
+kL59C1izKjcRN429AlKdshwhWbasvyL7sthI757zm1DfZTdTIctDWlKbYeG/tS7QyAgI3gcfrTHPhu1E1lKCBw==
+e3LpgB2RZ/E/BCxPJsn+TDDyxGYzrIsMyDt//KhJCjIQD6pNUxr5M5jrq2OwQZgwmz91YcmoQ2XRQAUDpe4BAw==
+w69bYgiQxDmCReB0Dugt9BstXlAKnwJkKCdWvCeZ9KnUCv0FJys6klzYk/O/b9t74tYhWZSX0bhETWHiwfpWBw==`;
+    })
+  
+    it('rawTxParseAndVerify', function(done){
+        assert.throws(
+            () => rawTxParseAndVerify(rawTx),
+            "Not same sum of inputs amount and outputs amount: (SourceAmount { amount: 235, base: 2 }, SourceAmount { amount: 315, base: 2 })"
+        );
+        //const tx = rawTxParseAndVerify(rawTx);
+        //assert.equal(tx.hash, "F26C0D9D77952D62085DE5D46A760EC53026D82464D6C9F3F2DAB8953B801934")
+        done();
+    });
+
+    it('rawTxParseAndVerify2', function(done){
+        const tx_ = rawTxParseAndVerify(`Version: 10
+Type: Transaction
+Currency: duniter_unit_test_currency
+Blockstamp: 6-1903D9F03FC7E14494FFD12296382E5EB4798D214B3A2CDE5C1E0D420E040A5C
+Locktime: 0
+Issuers:
+HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd
+Inputs:
+100:0:D:HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd:3
+Unlocks:
+0:SIG(0)
+Outputs:
+10:0:SIG(2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc)
+90:0:SIG(HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd)
+Comment: 
+sfGOC9anaIDGjbtHri+SvbD7AiIvhWOcOFu41yP+7R94Y7EvTxtR++Qa4SANihkWMtnpamEn5/KbTqu7tQrDDg==
+`);
+
+        assert.equal(tx_.signatures[0], "sfGOC9anaIDGjbtHri+SvbD7AiIvhWOcOFu41yP+7R94Y7EvTxtR++Qa4SANihkWMtnpamEn5/KbTqu7tQrDDg==")
+        done();
+    });
+});
\ No newline at end of file
diff --git a/test/unit-tools.ts b/test/unit-tools.ts
index e7bbf1aba..3d2f82c47 100644
--- a/test/unit-tools.ts
+++ b/test/unit-tools.ts
@@ -31,9 +31,9 @@ export async function shouldNotFail<T>(promise:Promise<T>) {
     await promise
   } catch(e) {
     let err = e;
-    if (typeof e === 'string') {
+    /*if (typeof e === 'string') {
       err = JSON.parse((e as any).message)
-    }
+    }*/
     should.not.exist(err);
   }
 }
-- 
GitLab