Skip to content

Commit 9b278b1

Browse files
committed
MimbleWimble: started implementing MW tx creation
Add NBitcoin.Secp256k1 library because it has Schnorr signature. As the library requires .netstandard 2.1, make NLitecoin target it instead of 2.0.
1 parent 5f5e144 commit 9b278b1

File tree

4 files changed

+400
-10
lines changed

4 files changed

+400
-10
lines changed
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
module NLitecoin.MimbleWimble.Pedersen
2+
3+
open Org.BouncyCastle.Crypto.Digests
4+
open Org.BouncyCastle.Crypto.Parameters
5+
open Org.BouncyCastle.Asn1.X9
6+
open Org.BouncyCastle.Math
7+
open NBitcoin
8+
9+
10+
let curve = ECNamedCurveTable.GetByName("secp256k1")
11+
let domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed())
12+
13+
let generatorG = curve.G
14+
let generatorH =
15+
curve.Curve.CreatePoint
16+
(BigInteger
17+
[| 0x50uy; 0x92uy; 0x9buy; 0x74uy; 0xc1uy; 0xa0uy; 0x49uy; 0x54uy; 0xb7uy; 0x8buy; 0x4buy; 0x60uy; 0x35uy; 0xe9uy; 0x7auy; 0x5euy;
18+
0x07uy; 0x8auy; 0x5auy; 0x0fuy; 0x28uy; 0xecuy; 0x96uy; 0xd5uy; 0x47uy; 0xbfuy; 0xeeuy; 0x9auy; 0xceuy; 0x80uy; 0x3auy; 0xc0uy; |],
19+
BigInteger
20+
[| 0x31uy; 0xd3uy; 0xc6uy; 0x86uy; 0x39uy; 0x73uy; 0x92uy; 0x6euy; 0x04uy; 0x9euy; 0x63uy; 0x7cuy; 0xb1uy; 0xb5uy; 0xf4uy; 0x0auy;
21+
0x36uy; 0xdauy; 0xc2uy; 0x8auy; 0xf1uy; 0x76uy; 0x69uy; 0x68uy; 0xc3uy; 0x0cuy; 0x23uy; 0x13uy; 0xf3uy; 0xa3uy; 0x89uy; 0x04uy; |])
22+
23+
let generatorJPub =
24+
curve.Curve.CreatePoint
25+
(BigInteger
26+
[|
27+
0x5fuy; 0x15uy; 0x21uy; 0x36uy; 0x93uy; 0x93uy; 0x01uy; 0x2auy; 0x8duy; 0x8buy; 0x39uy; 0x7euy; 0x9buy; 0xf4uy; 0x54uy; 0x29uy;
28+
0x2fuy; 0x5auy; 0x1buy; 0x3duy; 0x38uy; 0x85uy; 0x16uy; 0xc2uy; 0xf3uy; 0x03uy; 0xfcuy; 0x95uy; 0x67uy; 0xf5uy; 0x60uy; 0xb8uy; |],
29+
BigInteger
30+
[|
31+
0x3auy; 0xc4uy; 0xc5uy; 0xa6uy; 0xdcuy; 0xa2uy; 0x01uy; 0x59uy; 0xfcuy; 0x56uy; 0xcfuy; 0x74uy; 0x9auy; 0xa6uy; 0xa5uy; 0x65uy;
32+
0x31uy; 0x6auy; 0xa5uy; 0x03uy; 0x74uy; 0x42uy; 0x3fuy; 0x42uy; 0x53uy; 0x8fuy; 0xaauy; 0x2cuy; 0xd3uy; 0x09uy; 0x3fuy; 0xa4uy; |])
33+
34+
/// Calculates the blinding factor x' = x + SHA256(xG+vH | xJ), used in the switch commitment x'G+vH.
35+
let BlindSwitch (blindingFactor: BlindingFactor) (amount: CAmount) : BlindingFactor =
36+
let hasher = Sha256Digest()
37+
38+
let x = blindingFactor.ToUInt256().ToBytes() |> BigInteger
39+
let v = amount.ToString() |> BigInteger
40+
/// xG + vH
41+
let commitSerialized = generatorG.Multiply(x).Add(generatorH.Multiply(v)).GetEncoded()
42+
hasher.BlockUpdate(commitSerialized, 0, commitSerialized.Length)
43+
44+
// xJ
45+
let xJ = generatorJPub.Multiply x
46+
let xJSerialized = xJ.GetEncoded true
47+
hasher.BlockUpdate(xJSerialized, 0, xJSerialized.Length)
48+
49+
let hash = Array.zeroCreate<byte> 32
50+
hasher.DoFinal(hash, 0) |> ignore
51+
52+
let result = x.Add(BigInteger hash)
53+
54+
result.ToByteArrayUnsigned()
55+
|> uint256
56+
|> BlindingFactor.BlindingFactor
57+
58+
/// Generates a pedersen commitment: *commit = blind * G + value * H. The blinding factor is 32 bytes.
59+
let Commit (value: CAmount) (blind: BlindingFactor) : PedersenCommitment =
60+
let result =
61+
generatorG.Multiply(blind.ToUInt256().ToBytes() |> BigInteger)
62+
.Add(generatorH.Multiply(BigInteger.ValueOf value))
63+
let bytes = result.GetEncoded()
64+
assert(bytes.Length = PedersenCommitment.NumBytes)
65+
PedersenCommitment(BigInt bytes)
66+
67+
let AddBlindingFactors (positive: array<BlindingFactor>) (negative: array<BlindingFactor>) : BlindingFactor =
68+
let sum (factors: array<BlindingFactor>) =
69+
factors
70+
|> Array.map (fun blind -> blind.ToUInt256().ToBytes() |> BigInteger)
71+
|> Array.fold (fun (a : BigInteger) b -> a.Add(b)) BigInteger.Zero
72+
73+
let result = (sum positive).Subtract(sum negative)
74+
75+
result.ToByteArrayUnsigned()
76+
|> uint256
77+
|> BlindingFactor.BlindingFactor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
module NLitecoin.MimbleWimble.TransactionBuilder
2+
3+
open System
4+
5+
open NBitcoin
6+
open Org.BouncyCastle.Math
7+
8+
type private Inputs =
9+
{
10+
TotalBlind: BlindingFactor
11+
TotalKey: uint256
12+
Inputs: array<Input>
13+
}
14+
15+
type private Outputs =
16+
{
17+
TotalBlind: BlindingFactor
18+
TotalKey: uint256
19+
Outputs: array<Output>
20+
Coins: array<NLitecoin.MimbleWimble.Coin>
21+
}
22+
23+
/// Creates a standard input with a stealth key (feature bit = 1)// Creates a standard input with a stealth key (feature bit = 1)
24+
let private CreateInput (outputId: Hash) (commitment: PedersenCommitment) (inputKey: uint256) (outputKey: uint256) =
25+
let features = InputFeatures.STEALTH_KEY_FEATURE_BIT
26+
27+
let inputPubKey = PublicKey(inputKey.ToBytes() |> BigInt)
28+
let outputPubKey = PublicKey(outputKey.ToBytes() |> BigInt)
29+
30+
// Hash keys (K_i||K_o)
31+
let keyHasher = Hasher()
32+
keyHasher.Append inputPubKey
33+
keyHasher.Append outputPubKey
34+
let keyHash = keyHasher.Hash().ToBytes()
35+
36+
// Calculate aggregated key k_agg = k_i + HASH(K_i||K_o) * k_o
37+
let sigKey =
38+
BigInteger(outputKey.ToBytes())
39+
.Multiply(BigInteger keyHash)
40+
.Add(BigInteger(inputKey.ToBytes()))
41+
42+
let msgHasher = Hasher()
43+
//msgHasher.Append features
44+
msgHasher.Append outputId
45+
let msgHash = msgHasher.Hash().ToBytes()
46+
47+
let schnorrSignature =
48+
// is this the right one?
49+
NBitcoin.Secp256k1.ECPrivKey.Create(sigKey.ToByteArrayUnsigned()).SignBIP340(msgHash)
50+
51+
{
52+
Features = features
53+
OutputID = outputId
54+
Commitment = commitment
55+
InputPublicKey = Some inputPubKey
56+
OutputPublicKey = outputPubKey
57+
Signature = Signature(schnorrSignature.ToBytes() |> BigInt)
58+
ExtraData = Array.empty
59+
}
60+
61+
let private CreateInputs (inputCoins: seq<NLitecoin.MimbleWimble.Coin>) : Inputs =
62+
let blinds, keys, inputs =
63+
[| for inputCoin in inputCoins do
64+
let blind = Pedersen.BlindSwitch inputCoin.Blind.Value inputCoin.Amount
65+
let ephemeralKey = NBitcoin.RandomUtils.GetUInt256()
66+
let input =
67+
CreateInput
68+
inputCoin.OutputId
69+
(Pedersen.Commit inputCoin.Amount blind)
70+
ephemeralKey
71+
inputCoin.SpendKey.Value
72+
yield blind, (BlindingFactor ephemeralKey, BlindingFactor inputCoin.SpendKey.Value), input |]
73+
|> Array.unzip3
74+
75+
let positiveKeys, negativeKeys = Array.unzip keys
76+
77+
{
78+
TotalBlind = Pedersen.AddBlindingFactors blinds Array.empty
79+
TotalKey = (Pedersen.AddBlindingFactors positiveKeys negativeKeys).ToUInt256()
80+
Inputs = inputs
81+
}
82+
83+
let private CreateOutput (senderPrivKey: uint256) (receiverAddr: StealthAddress) (value: uint64) : Output * BlindingFactor =
84+
let features = OutputFeatures.STANDARD_FIELDS_FEATURE_BIT
85+
86+
// Generate 128-bit secret nonce 'n' = Hash128(T_nonce, sender_privkey)
87+
let n =
88+
let hasher = Hasher(HashTags.NONCE)
89+
hasher.Write(senderPrivKey.ToBytes())
90+
hasher.Hash().ToBytes()
91+
|> Array.take 16
92+
|> BigInt
93+
94+
// Calculate unique sending key 's' = H(T_send, A, B, v, n)
95+
let s =
96+
let hasher = Hasher(HashTags.SEND_KEY)
97+
hasher.Append receiverAddr.ScanPubKey
98+
hasher.Append receiverAddr.SpendPubKey
99+
hasher.Write (BitConverter.GetBytes value)
100+
hasher.Append n
101+
hasher.Hash().ToBytes()
102+
|> BigInteger
103+
104+
let A =
105+
match receiverAddr.ScanPubKey with
106+
| PublicKey pubKey -> BigInteger pubKey.Data
107+
108+
let B =
109+
match receiverAddr.SpendPubKey with
110+
| PublicKey pubKey -> BigInteger pubKey.Data
111+
112+
// Derive shared secret 't' = H(T_derive, s*A)
113+
let sA = A.Multiply s
114+
let t =
115+
let hasher = Hasher(HashTags.DERIVE)
116+
hasher.Write(sA.ToByteArrayUnsigned())
117+
hasher.Hash()
118+
119+
// Construct one-time public key for receiver 'Ko' = H(T_outkey, t)*B
120+
let Ko =
121+
let hasher = Hasher(HashTags.OUT_KEY)
122+
hasher.Append t
123+
B.Multiply(hasher.Hash().ToBytes() |> BigInteger);
124+
125+
// Key exchange public key 'Ke' = s*B
126+
let Ke = B.Multiply s
127+
128+
// Calc blinding factor and mask nonce and amount.
129+
let mask = OutputMask.FromShared(t.ToUInt256())
130+
let blind = Pedersen.BlindSwitch mask.PreBlind (int64 value)
131+
let mv = mask.MaskValue value
132+
let mn = mask.MaskNonce n
133+
134+
// Commitment 'C' = r*G + v*H
135+
let outputCommit = Pedersen.Commit (int64 value) blind
136+
137+
// Calculate the ephemeral send pubkey 'Ks' = ks*G
138+
let Ks = Secp256k1.ECPubKey.Create(senderPrivKey.ToBytes())
139+
140+
// Derive view tag as first byte of H(T_tag, sA)
141+
let viewTag =
142+
let hasher = Hasher(HashTags.TAG)
143+
hasher.Write(sA.ToByteArrayUnsigned())
144+
hasher.Hash().ToBytes().[0]
145+
146+
let message =
147+
{
148+
Features = features
149+
StandardFields =
150+
Some {
151+
KeyExchangePubkey = PublicKey(Ke.ToByteArrayUnsigned() |> BigInt)
152+
ViewTag = viewTag
153+
MaskedValue = mv
154+
MaskedNonce = mn
155+
}
156+
ExtraData = Array.empty
157+
}
158+
159+
failwith "not implemented"
160+
161+
let private CreateOutputs (recipients: seq<Recipient>) : Outputs =
162+
let outputBlinds, outputs, coins =
163+
[| for recipient in recipients do
164+
let ephemeralKey = NBitcoin.RandomUtils.GetUInt256()
165+
let output, rawBlind = CreateOutput ephemeralKey recipient.Address (uint64 recipient.Amount)
166+
let outputBlind = Pedersen.BlindSwitch rawBlind recipient.Amount
167+
let coin =
168+
{ Coin.Empty with
169+
Blind = Some rawBlind
170+
Amount = recipient.Amount
171+
OutputId = output.GetOutputID()
172+
SenderKey = Some ephemeralKey
173+
Address = Some recipient.Address
174+
}
175+
yield outputBlind, output, coin
176+
|]
177+
|> Array.unzip3
178+
179+
let outputKeys =
180+
coins
181+
|> Array.choose (fun coin -> coin.SenderKey)
182+
|> Array.map BlindingFactor
183+
184+
{
185+
TotalBlind = Pedersen.AddBlindingFactors outputBlinds Array.empty
186+
TotalKey = (Pedersen.AddBlindingFactors outputKeys Array.empty).ToUInt256()
187+
Outputs = outputs
188+
Coins = coins
189+
}

0 commit comments

Comments
 (0)