diff --git a/src/NLitecoin/MimbleWimble/Pedersen.fs b/src/NLitecoin/MimbleWimble/Pedersen.fs new file mode 100644 index 0000000..31fcea3 --- /dev/null +++ b/src/NLitecoin/MimbleWimble/Pedersen.fs @@ -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 32 + hasher.DoFinal(hash, 0) |> ignore + + let result = x.Add(BigInteger hash) + + result.ToByteArrayUnsigned() + |> uint256 + |> BlindingFactor.BlindindgFactor + +/// 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) (negative: array) : BlindingFactor = + let sum (factors: array) = + 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.BlindindgFactor diff --git a/src/NLitecoin/MimbleWimble/TransactionBuilder.fs b/src/NLitecoin/MimbleWimble/TransactionBuilder.fs new file mode 100644 index 0000000..301af1b --- /dev/null +++ b/src/NLitecoin/MimbleWimble/TransactionBuilder.fs @@ -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 + } + +type private Outputs = + { + TotalBlind: BlindingFactor + TotalKey: uint256 + Outputs: array + Coins: array + } + +/// 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) : 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, (BlindindgFactor ephemeralKey, BlindindgFactor 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) : 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 BlindindgFactor + + { + TotalBlind = Pedersen.AddBlindingFactors outputBlinds Array.empty + TotalKey = (Pedersen.AddBlindingFactors outputKeys Array.empty).ToUint256() + Outputs = outputs + Coins = coins + } diff --git a/src/NLitecoin/MimbleWimble/Types.fs b/src/NLitecoin/MimbleWimble/Types.fs index 875ac87..99b825b 100644 --- a/src/NLitecoin/MimbleWimble/Types.fs +++ b/src/NLitecoin/MimbleWimble/Types.fs @@ -63,6 +63,10 @@ module Helpers = type BlindingFactor = | BlindingFactor of uint256 + member self.ToUInt256() = + match self with + | BlindingFactor number -> number + static member Read(stream: BitcoinStream) : BlindingFactor = BlindingFactor(readUint256 stream) @@ -73,6 +77,13 @@ type BlindingFactor = type Hash = | Hash of uint256 + member self.ToUInt256() = + match self with + | Hash number -> number + + member self.ToBytes() = + self.ToUInt256().ToBytes() + static member Read(stream: BitcoinStream) : Hash = readUint256 stream |> Hash @@ -209,6 +220,7 @@ type Input = ExtraData: array Signature: Signature } + static member Read(stream: BitcoinStream) : Input = assert(not stream.Serializing) let featuresByte = ref 0uy @@ -350,6 +362,67 @@ type RangeProof = assert(bytes.Length = RangeProof.Size) stream.ReadWrite(ref bytes) +type StealthAddress = + { + ScanPubKey: PublicKey + SpendPubKey: PublicKey + } + static member Random() = + let scanPubKeyBytes = Array.zeroCreate PublicKey.NumBytes + NBitcoin.RandomUtils.Random.GetBytes scanPubKeyBytes + let spendPubKeyBytes = Array.zeroCreate PublicKey.NumBytes + NBitcoin.RandomUtils.Random.GetBytes spendPubKeyBytes + { + ScanPubKey = PublicKey(BigInt scanPubKeyBytes) + SpendPubKey = PublicKey(BigInt spendPubKeyBytes) + } + +type OutputMask = + { + PreBlind: BlindingFactor + ValueMask: uint64 + NonceMask: BigInt + } + static member NonceMaskNumBytes = 16 + + /// Feeds the shared secret 't' into tagged hash functions to derive: + /// q - the blinding factor + /// v' - the value mask + /// n' - the nonce mask + static member FromShared (sharedSecret: uint256) = + let preBlind = + let hasher = Hasher(HashTags.BLIND) + hasher.Write(sharedSecret.ToBytes()) + hasher.Hash().ToUInt256() + |> BlindingFactor.BlindingFactor + let valueMask = + let hasher = Hasher(HashTags.VALUE_MASK) + hasher.Write(sharedSecret.ToBytes()) + hasher.Hash().ToBytes() + |> Array.take 8 + |> BitConverter.ToUInt64 + let nonceMask = + let hasher = Hasher(HashTags.NONCE_MASK) + hasher.Write(sharedSecret.ToBytes()) + hasher.Hash().ToBytes() + |> Array.take OutputMask.NonceMaskNumBytes + |> BigInt + { + PreBlind = preBlind + ValueMask = valueMask + NonceMask = nonceMask + } + + member self.MaskValue (value: uint64) = + value ^^^ self.ValueMask + + member self.MaskNonce (nonce: BigInt) = + Array.map2 + (^^^) + nonce.Data + self.NonceMask.Data + |> BigInt + type Output = { Commitment: PedersenCommitment @@ -381,6 +454,16 @@ type Output = write stream self.RangeProof write stream self.Signature + member self.GetOutputID() : Hash = + let hasher = Hasher() + hasher.Append self.Commitment + hasher.Append self.SenderPublicKey + hasher.Append self.ReceiverPublicKey + hasher.Append(Hasher.CalculateHash self.Message) + hasher.Append(Hasher.CalculateHash self.RangeProof) + hasher.Append self.Signature + hasher.Hash() + type KernelFeatures = | FEE_FEATURE_BIT = 0x01 | PEGIN_FEATURE_BIT = 0x02 @@ -560,20 +643,58 @@ type Transaction = let binaryTx = encoder.DecodeData txString use memoryStream = new MemoryStream(binaryTx) let bitcoinStream = new BitcoinStream(memoryStream, false) - Transaction.Read bitcoinStream + let result = Transaction.Read bitcoinStream + result static member Read(stream: BitcoinStream) : Transaction = - let result = - { - KernelOffset = BlindingFactor.Read stream - StealthOffset = BlindingFactor.Read stream - Body = TxBody.Read stream - } - - result + { + KernelOffset = BlindingFactor.Read stream + StealthOffset = BlindingFactor.Read stream + Body = TxBody.Read stream + } interface ISerializeable with member self.Write(stream) = self.KernelOffset |> write stream self.StealthOffset |> write stream self.Body |> write stream + +/// Represents an output owned by the wallet, and the keys necessary to spend it. +/// See https://github.com/litecoin-project/litecoin/blob/master/src/libmw/include/mw/models/wallet/Coin.h +type Coin = + { + AddressIndex: uint32 + SpendKey: Option + Blind: Option + Amount: CAmount + OutputId: Hash + SenderKey: Option + Address: Option + SharedSecret: Option + } + static member ChangeIndex = 0u + static member PeginIndex = 1u + static member CustomKey = UInt32.MaxValue - 1u + static member UnknownIndex = UInt32.MaxValue + + member self.IsChange = self.AddressIndex = Coin.ChangeIndex + member self.IsPegIn = self.AddressIndex = Coin.PeginIndex + member self.IsMine = self.AddressIndex <> Coin.UnknownIndex + + static member Empty = + { + AddressIndex = Coin.UnknownIndex + SpendKey = None + Blind = None + Amount = 0L + OutputId = Hash(uint256 0UL) + SenderKey = None + Address = None + SharedSecret = None + } + +type Recipient = + { + Amount: CAmount + Address: StealthAddress + } diff --git a/src/NLitecoin/NLitecoin.fsproj b/src/NLitecoin/NLitecoin.fsproj index 02c5146..d394045 100644 --- a/src/NLitecoin/NLitecoin.fsproj +++ b/src/NLitecoin/NLitecoin.fsproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 true 0.1.0.0 0.1.0.0 @@ -13,6 +13,8 @@ + + @@ -26,6 +28,7 @@ +