The old design of Gotts Non-Interactive Transaction has been completely implemented on node version v0.0.6 and wallet version v0.0.6 (but missing the necessary ComSig signature for each output).
It use the Bitcoin style Public-Key-Hash to lock the output. To spend a Non-Interactive Output, a Signature with the corresponding revealed Public Key must be provided as the unlocker. This is an intuitive and simple design, since the concept of P2PKH (Pay-To-Public-Key-Hash) is well known in crypto world.
But the cost of combining the Bitcoin-style P2PKH with Mimblewimble is not trivial. The NIT (Non-Interactive Transaction) output need a OutputLocker
structure which is 65-bytes
bigger than original Mimblewimble native output. And when spending this NIT output, an additional InputUnlocker
is needed for unlocking it, which is 104-bytes
length. Considering the typical Gotts interactive transaction size is just 244
bytes, neither OutputLocker
nor InputUnlocker
is trivial for Gotts transaction.
Let's recall these two data structure here:
struct OutputLocker {
/// The Hash of 'Pay-to-Public-Key-Hash'.
p2pkh: Hash,
/// The 'R' for ephemeral key: `q = Hash(secured_w || p*R)`.
R: PublicKey,
/// A secured path message which hide the key derivation path and the random w of commitment.
spath: SecuredPath,
}
struct InputUnlocker {
/// Nonce for the signing message.
nonce: u64,
/// The signature for the output which has a locked public key / address.
sig: Signature,
/// The public key.
pub_key: PublicKey,
}
Some examples here for the old design, taken from the working wallet version v0.0.6.
An Example of OutputLocker
{
"p2pkh": "4c4527db7dc4ac60146dd43c8755f9162d9020394b7f138248716dc8ea5bb167",
"R": "03e54c4fecb14e86b08d837e68fc257c8c7e21484387b7779111e74fb08fdb5a7c",
"spath": "cd2ce94830ab80707d85f452"
}
An Example of InputUnlocker
{
"nonce": 16182884338189852768,
"sig": "fa86727f851bb0ad68ed7b5b60f5350e9d5ff584dc378587a8a68862b3682cd0f46e4e4eb4375d77bbdbb4c818c10f8bb6cd70b183ff1faae9202dd44ecd649e",
"pub_key": "02b25ba03e337726d106adcbd5ddb26615f40dc2474d212562c11df03a6eb343a7"
}
A Complete Example of a Non-Interactive Transaction (1 Input without the _Unlocker_ and 2 Outputs)
{
"body": {
"inputs": [
{
"SingleInput": {
"features": "Plain",
"commit": "08f74968697788c7fc3c2c0946f96b9fc727fdc2fc6a8c8e7c13d2d070f9be679b"
}
}
],
"outputs": [
{
"features": {
"Plain": {
"spath": "09912c2a6f86f3743f32b08b"
}
},
"commit": "0970610d308ef79c3cdb9d1389b55e84a24ed309bb3f341208cb081fa8dbc93388",
"value": 592540000000
},
{
"features": {
"SigLocked": {
"locker": {
"p2pkh": "5245844436d6006bdd2832d6d14d321dd23c8702f5daae50166a755fcb9630f3",
"R": "03693534e9d96785ebfebf81decba1408e38d6c0570b3a4f5d0cf8689263f732db",
"spath": "733b8d1342e4b997eb5287e7"
}
}
},
"commit": "09e28b3c50ba19c7c1f43cebd934dd1a677d96eb110ab23ef56f5faf8f7205aee7",
"value": 199310000000
}
],
"kernels": [
{
"features": {
"Plain": {
"fee": 8000000
}
},
"excess": "09356dd90cce15bcea03642c16ac94c4f35aff49e6bb1613adb420506ceb5d9d93",
"excess_sig": "c940db5286d6ec46523565905194174f62e4b01bfca9e25d687f828cc4aa8ffd4138c459c7eb90f649758c37aff753d39e01446c272bc29e12e87a3c6694eb9f"
}
]
}
}
The transaction size of above non-interactive transaction example (1 Input without the Unlocker and 2 Outputs) is 12+(1+1+33)+(1+12+33+8)+(1+32+33+12+33+8)+(1+4+33+64)=322
bytes. (with the missing ComSig signature, that will be 514 bytes.)
A Complete Example of a Non-Interactive Transaction (1 Input with the _Unlocker_ and 2 Outputs)
{
"body": {
"inputs": [
{
"InputsWithUnlocker": {
"inputs": [
{
"features": "SigLocked",
"commit": "08e79e44557a62f53d37c72194f8e381030da80294c18820326280ee398eec39c3"
}
],
"unlocker": {
"nonce": 2135360374869037106,
"sig": "48d4ec103c73afd05311ea791698ff447b9b0bf1872b656d231faad05274b4d4c6525ac41255ad1ec164c6f79860a71dcd418daf05f0c1c848a6067c5a1d96c5",
"pub_key": "02ad6b84c9cf5a71783be1095f6946cfb17541e548080496488d86a487f359102f"
}
}
}
],
"outputs": [
{
"features": {
"Plain": {
"spath": "cb8baac824fd75cca046051a"
}
},
"commit": "0871577dc1d3e9467a38eac68427444a7195f5336e4c1c25d614069bf052dfddbe",
"value": 899992000000
},
{
"features": {
"SigLocked": {
"locker": {
"p2pkh": "b33530e87b76a641806fe6a448efdbaf61db62e8c350fe57dddd58929f7ade28",
"R": "035e589c25fc6b0dfe4951d108addc777c0fe4cf59ea0b0d85430d0f5df1c5f36f",
"spath": "5bff98d7a2fc769575b2ec65"
}
}
},
"commit": "08486f23af753e46c53c1557507848cb774909c0569f30514f6e1e8082a03980e4",
"value": 100000000000
}
],
"kernels": [
{
"features": {
"Plain": {
"fee": 8000000
}
},
"excess": "099f364954fc555e6cae867969c0129eb3fabceebdd32684d5e0d6acf8510d6edd",
"excess_sig": "aa48e889a50f4a186fc9f35f31337948b2e3c636d293f81635c387d507b760026e4087f41b21978f1c462a5b322962ca5684283b3041177302c0be4056900f06"
}
]
}
}
The transaction size of above non-interactive transaction example (1 Input with the Unlocker and 2 Outputs) is 426 bytes. (with the missing ComSig signature, that will be 618 bytes.)
Recently, I have been thinking whether it will be a better design to change back to pure Mimblewimble style "locker", i.e. the Pedersen Commitment, to get a more compact Non-Interactive Transaction solution.
If considering the classic PKH (Public Key Hash) or Script Hash, i.e. the typical Bitcoin-style "locker", is an efficient way for locking, the Pedersen Commitment could be a better way, which can "lock" both the owner's Public Key and the output value, and it's capable to lock multiple Public Keys.
There's a ComSig signature scheme, which is a simple Schnorr signature with Pedersen commitment as key.
The procedure of a ComSig, for a Pedersen commitment C=x*G+w*H
,is:
- Select two random numbers
k1
andk2
, calculate a Pedersen commitmentR=k1*G+k2*H
. - Calculate
e = Hash(R || C || m)
, wherem
is the signing message. - Calculate
k1 + e*x
asu
, calculatek2 + e*w
asv
. - Done. The signature is
(R,u,v)
, for Pedersen Commitmentx*G + w*H
as signature public key.
Then anyone can verify this signature with the open Pedersen Commitment C = x*G + w*H
, if:
u*G + v*H = R + e*C
In a blockchain system, address is an essential primitive which is used in transaction. The Stealth Address scheme is designed to protect the recipient privacy.
A prerequisite research on the stealth address has been done here and the Gotts Stealth Address is designed as (Ai,Bi,i), where Ai is the recipient public spend key and Ai is the recipient public view key, i is the child number which is within [0,231-1].
A prerequisite research has been done here.
With this non-interactive transaction scheme, the Output data structure in Gotts may be defined as:
struct Output {
features: OutputFeatures,
commit: Commitment,
value: u64,
R: PublicKey,
sig: ComSignature,
spath: SecuredPath
}
The size of this Output data structure is 1+33+8+33+96+12 = 183
bytes.
And the Transaction data structure may be defined as:
struct Transaction {
excess: Commitment,
inputs: Vec<InputEx>,
outputs: Vec<Output>,
}
struct Input {
features: OutputFeatures,
commit: Commitment,
}
struct InputEx {
inputs: Vec<Input>,
pubs: Vec<PublicKey>,
sig: Signature,
}
The size of this InputEx data structure is 64+(34+33)*n+4
bytes. In case of single input, this size is 135 bytes.
In Gotts, the TxKernel
is same as the original Mimblewimble protocol.
The final size of a typical transaction with 1 Input and 2 Outputs in Gotts will be about 644 bytes. This is less than the old non-interactive transaction scheme, even with the added Stealth Address feature, especially the simple structure and the clear security model, the new non-interactive transaction scheme is much better.
Size (Bytes) | Old Interactive Transaction | Old Non-Interactive Transaction | New Non-Interactive Transaction |
---|---|---|---|
1 payment with 1 Input | 436 | 618 | 644 |
Notes:
- Suppose the old non-interactive transaction include InputUnlocker, meaning the Input is a non-interactive transaction output.
- Suppose the old interactive transaction doesn't include any InputUnlocker, meaning the Input is not a non-interactive transaction output.
The old interactive transaction scheme has the optimal transaction size, but since the size is near to the new non-interactive transaction and the increased complexity to support two types of transactions, especially the bad usability of the interactive transaction for end user, therefore decided to remove).
The detail sizes of the Gotts transaction with 1 Input 2 Outputs:
Name | Size (bytes) |
---|---|
excess | 33 |
~~~ | ~~~ |
InputEx vector size | 4 |
Input vector size | 4 |
1 Input | 34 |
1 p*R | 33 |
InputEx signature | 64 |
Output vector size | 4 |
2 Output | 183*2=366 |
1 TxKernel | 102 |
~~~ | ~~~ |
Total | 644 |
The detail sizes of the Gotts output:
Name | Size (bytes) |
---|---|
feature flag | 1 |
commitment | 33 |
value | 8 |
R | 33 |
output ComSig signature | 96 |
secured path | 12 |
~~~ | ~~~ |
Total | 183 |
PS. Data Structures:
struct Transaction {
inputs: Vec<InputEx>,
outputs: Vec<Output>,
kernels: Vec<TxKernel>
}
struct Input {
features: OutputFeatures,
commit: Commitment
}
struct InputEx {
inputs: Vec<Input>,
pubs: Vec<PublicKey>,
sig: Signature
}
struct Output {
features: OutputFeatures,
commit: Commitment,
value: u64,
R: PublicKey,
sig: ComSignature,
spath: SecuredPath
}