Skip to content

Commit

Permalink
MimbleWimble: started implementing MW tx creation
Browse files Browse the repository at this point in the history
Add NBitcoin.Secp256k1 library because it has Schnorr
signature. As the library requires .netstandard 2.1,
make NLitecoin target it instead of 2.0.
  • Loading branch information
webwarrior-ws committed Mar 26, 2024
1 parent 5f5e144 commit 9b278b1
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 10 deletions.
77 changes: 77 additions & 0 deletions src/NLitecoin/MimbleWimble/Pedersen.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module NLitecoin.MimbleWimble.Pedersen

open Org.BouncyCastle.Crypto.Digests
open Org.BouncyCastle.Crypto.Parameters
open Org.BouncyCastle.Asn1.X9
open Org.BouncyCastle.Math
open NBitcoin


let curve = ECNamedCurveTable.GetByName("secp256k1")
let domainParams = new ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed())

let generatorG = curve.G
let generatorH =
curve.Curve.CreatePoint
(BigInteger
[| 0x50uy; 0x92uy; 0x9buy; 0x74uy; 0xc1uy; 0xa0uy; 0x49uy; 0x54uy; 0xb7uy; 0x8buy; 0x4buy; 0x60uy; 0x35uy; 0xe9uy; 0x7auy; 0x5euy;
0x07uy; 0x8auy; 0x5auy; 0x0fuy; 0x28uy; 0xecuy; 0x96uy; 0xd5uy; 0x47uy; 0xbfuy; 0xeeuy; 0x9auy; 0xceuy; 0x80uy; 0x3auy; 0xc0uy; |],
BigInteger
[| 0x31uy; 0xd3uy; 0xc6uy; 0x86uy; 0x39uy; 0x73uy; 0x92uy; 0x6euy; 0x04uy; 0x9euy; 0x63uy; 0x7cuy; 0xb1uy; 0xb5uy; 0xf4uy; 0x0auy;
0x36uy; 0xdauy; 0xc2uy; 0x8auy; 0xf1uy; 0x76uy; 0x69uy; 0x68uy; 0xc3uy; 0x0cuy; 0x23uy; 0x13uy; 0xf3uy; 0xa3uy; 0x89uy; 0x04uy; |])

let generatorJPub =
curve.Curve.CreatePoint
(BigInteger
[|
0x5fuy; 0x15uy; 0x21uy; 0x36uy; 0x93uy; 0x93uy; 0x01uy; 0x2auy; 0x8duy; 0x8buy; 0x39uy; 0x7euy; 0x9buy; 0xf4uy; 0x54uy; 0x29uy;
0x2fuy; 0x5auy; 0x1buy; 0x3duy; 0x38uy; 0x85uy; 0x16uy; 0xc2uy; 0xf3uy; 0x03uy; 0xfcuy; 0x95uy; 0x67uy; 0xf5uy; 0x60uy; 0xb8uy; |],
BigInteger
[|
0x3auy; 0xc4uy; 0xc5uy; 0xa6uy; 0xdcuy; 0xa2uy; 0x01uy; 0x59uy; 0xfcuy; 0x56uy; 0xcfuy; 0x74uy; 0x9auy; 0xa6uy; 0xa5uy; 0x65uy;
0x31uy; 0x6auy; 0xa5uy; 0x03uy; 0x74uy; 0x42uy; 0x3fuy; 0x42uy; 0x53uy; 0x8fuy; 0xaauy; 0x2cuy; 0xd3uy; 0x09uy; 0x3fuy; 0xa4uy; |])

/// Calculates the blinding factor x' = x + SHA256(xG+vH | xJ), used in the switch commitment x'G+vH.
let BlindSwitch (blindingFactor: BlindingFactor) (amount: CAmount) : BlindingFactor =
let hasher = Sha256Digest()

let x = blindingFactor.ToUInt256().ToBytes() |> BigInteger
let v = amount.ToString() |> BigInteger
/// xG + vH
let commitSerialized = generatorG.Multiply(x).Add(generatorH.Multiply(v)).GetEncoded()
hasher.BlockUpdate(commitSerialized, 0, commitSerialized.Length)

// xJ
let xJ = generatorJPub.Multiply x
let xJSerialized = xJ.GetEncoded true
hasher.BlockUpdate(xJSerialized, 0, xJSerialized.Length)

let hash = Array.zeroCreate<byte> 32
hasher.DoFinal(hash, 0) |> ignore

let result = x.Add(BigInteger hash)

result.ToByteArrayUnsigned()
|> uint256
|> BlindingFactor.BlindingFactor

/// Generates a pedersen commitment: *commit = blind * G + value * H. The blinding factor is 32 bytes.
let Commit (value: CAmount) (blind: BlindingFactor) : PedersenCommitment =
let result =
generatorG.Multiply(blind.ToUInt256().ToBytes() |> BigInteger)
.Add(generatorH.Multiply(BigInteger.ValueOf value))
let bytes = result.GetEncoded()
assert(bytes.Length = PedersenCommitment.NumBytes)
PedersenCommitment(BigInt bytes)

let AddBlindingFactors (positive: array<BlindingFactor>) (negative: array<BlindingFactor>) : BlindingFactor =
let sum (factors: array<BlindingFactor>) =
factors
|> Array.map (fun blind -> blind.ToUInt256().ToBytes() |> BigInteger)
|> Array.fold (fun (a : BigInteger) b -> a.Add(b)) BigInteger.Zero

let result = (sum positive).Subtract(sum negative)

result.ToByteArrayUnsigned()
|> uint256
|> BlindingFactor.BlindingFactor
189 changes: 189 additions & 0 deletions src/NLitecoin/MimbleWimble/TransactionBuilder.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
module NLitecoin.MimbleWimble.TransactionBuilder

open System

open NBitcoin
open Org.BouncyCastle.Math

type private Inputs =
{
TotalBlind: BlindingFactor
TotalKey: uint256
Inputs: array<Input>
}

type private Outputs =
{
TotalBlind: BlindingFactor
TotalKey: uint256
Outputs: array<Output>
Coins: array<NLitecoin.MimbleWimble.Coin>
}

/// Creates a standard input with a stealth key (feature bit = 1)// Creates a standard input with a stealth key (feature bit = 1)
let private CreateInput (outputId: Hash) (commitment: PedersenCommitment) (inputKey: uint256) (outputKey: uint256) =
let features = InputFeatures.STEALTH_KEY_FEATURE_BIT

let inputPubKey = PublicKey(inputKey.ToBytes() |> BigInt)
let outputPubKey = PublicKey(outputKey.ToBytes() |> BigInt)

// Hash keys (K_i||K_o)
let keyHasher = Hasher()
keyHasher.Append inputPubKey
keyHasher.Append outputPubKey
let keyHash = keyHasher.Hash().ToBytes()

// Calculate aggregated key k_agg = k_i + HASH(K_i||K_o) * k_o
let sigKey =
BigInteger(outputKey.ToBytes())
.Multiply(BigInteger keyHash)
.Add(BigInteger(inputKey.ToBytes()))

let msgHasher = Hasher()
//msgHasher.Append features
msgHasher.Append outputId
let msgHash = msgHasher.Hash().ToBytes()

let schnorrSignature =
// is this the right one?
NBitcoin.Secp256k1.ECPrivKey.Create(sigKey.ToByteArrayUnsigned()).SignBIP340(msgHash)

{
Features = features
OutputID = outputId
Commitment = commitment
InputPublicKey = Some inputPubKey
OutputPublicKey = outputPubKey
Signature = Signature(schnorrSignature.ToBytes() |> BigInt)
ExtraData = Array.empty
}

let private CreateInputs (inputCoins: seq<NLitecoin.MimbleWimble.Coin>) : Inputs =
let blinds, keys, inputs =
[| for inputCoin in inputCoins do
let blind = Pedersen.BlindSwitch inputCoin.Blind.Value inputCoin.Amount
let ephemeralKey = NBitcoin.RandomUtils.GetUInt256()
let input =
CreateInput
inputCoin.OutputId
(Pedersen.Commit inputCoin.Amount blind)
ephemeralKey
inputCoin.SpendKey.Value
yield blind, (BlindingFactor ephemeralKey, BlindingFactor inputCoin.SpendKey.Value), input |]
|> Array.unzip3

let positiveKeys, negativeKeys = Array.unzip keys

{
TotalBlind = Pedersen.AddBlindingFactors blinds Array.empty
TotalKey = (Pedersen.AddBlindingFactors positiveKeys negativeKeys).ToUInt256()
Inputs = inputs
}

let private CreateOutput (senderPrivKey: uint256) (receiverAddr: StealthAddress) (value: uint64) : Output * BlindingFactor =
let features = OutputFeatures.STANDARD_FIELDS_FEATURE_BIT

// Generate 128-bit secret nonce 'n' = Hash128(T_nonce, sender_privkey)
let n =
let hasher = Hasher(HashTags.NONCE)
hasher.Write(senderPrivKey.ToBytes())
hasher.Hash().ToBytes()
|> Array.take 16
|> BigInt

// Calculate unique sending key 's' = H(T_send, A, B, v, n)
let s =
let hasher = Hasher(HashTags.SEND_KEY)
hasher.Append receiverAddr.ScanPubKey
hasher.Append receiverAddr.SpendPubKey
hasher.Write (BitConverter.GetBytes value)
hasher.Append n
hasher.Hash().ToBytes()
|> BigInteger

let A =
match receiverAddr.ScanPubKey with
| PublicKey pubKey -> BigInteger pubKey.Data

let B =
match receiverAddr.SpendPubKey with
| PublicKey pubKey -> BigInteger pubKey.Data

// Derive shared secret 't' = H(T_derive, s*A)
let sA = A.Multiply s
let t =
let hasher = Hasher(HashTags.DERIVE)
hasher.Write(sA.ToByteArrayUnsigned())
hasher.Hash()

// Construct one-time public key for receiver 'Ko' = H(T_outkey, t)*B
let Ko =
let hasher = Hasher(HashTags.OUT_KEY)
hasher.Append t
B.Multiply(hasher.Hash().ToBytes() |> BigInteger);

// Key exchange public key 'Ke' = s*B
let Ke = B.Multiply s

// Calc blinding factor and mask nonce and amount.
let mask = OutputMask.FromShared(t.ToUInt256())
let blind = Pedersen.BlindSwitch mask.PreBlind (int64 value)
let mv = mask.MaskValue value
let mn = mask.MaskNonce n

// Commitment 'C' = r*G + v*H
let outputCommit = Pedersen.Commit (int64 value) blind

// Calculate the ephemeral send pubkey 'Ks' = ks*G
let Ks = Secp256k1.ECPubKey.Create(senderPrivKey.ToBytes())

// Derive view tag as first byte of H(T_tag, sA)
let viewTag =
let hasher = Hasher(HashTags.TAG)
hasher.Write(sA.ToByteArrayUnsigned())
hasher.Hash().ToBytes().[0]

let message =
{
Features = features
StandardFields =
Some {
KeyExchangePubkey = PublicKey(Ke.ToByteArrayUnsigned() |> BigInt)
ViewTag = viewTag
MaskedValue = mv
MaskedNonce = mn
}
ExtraData = Array.empty
}

failwith "not implemented"

let private CreateOutputs (recipients: seq<Recipient>) : Outputs =
let outputBlinds, outputs, coins =
[| for recipient in recipients do
let ephemeralKey = NBitcoin.RandomUtils.GetUInt256()
let output, rawBlind = CreateOutput ephemeralKey recipient.Address (uint64 recipient.Amount)
let outputBlind = Pedersen.BlindSwitch rawBlind recipient.Amount
let coin =
{ Coin.Empty with
Blind = Some rawBlind
Amount = recipient.Amount
OutputId = output.GetOutputID()
SenderKey = Some ephemeralKey
Address = Some recipient.Address
}
yield outputBlind, output, coin
|]
|> Array.unzip3

let outputKeys =
coins
|> Array.choose (fun coin -> coin.SenderKey)
|> Array.map BlindingFactor

{
TotalBlind = Pedersen.AddBlindingFactors outputBlinds Array.empty
TotalKey = (Pedersen.AddBlindingFactors outputKeys Array.empty).ToUInt256()
Outputs = outputs
Coins = coins
}
Loading

0 comments on commit 9b278b1

Please sign in to comment.