• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

meshtastic / c-sharp / 17021068173

17 Aug 2025 12:34PM UTC coverage: 53.023% (-6.7%) from 59.738%
17021068173

Pull #123

github

web-flow
Merge 33f1eb888 into 50bb5fc3a
Pull Request #123: Testing framework WIP and XeDSA signing infra

224 of 696 branches covered (32.18%)

Branch coverage included in aggregate %.

167 of 461 new or added lines in 4 files covered. (36.23%)

1 existing line in 1 file now uncovered.

1118 of 1835 relevant lines covered (60.93%)

4.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.81
/Meshtastic/Crypto/XEdDSASigning.cs
1
using System.Security.Cryptography;
2
using Org.BouncyCastle.Crypto.Signers;
3
using Org.BouncyCastle.Crypto.Parameters;
4
using Org.BouncyCastle.Security;
5
using Org.BouncyCastle.Crypto.Generators;
6
using Org.BouncyCastle.Crypto;
7
using Org.BouncyCastle.Math;
8
using System.Text;
9

10
namespace Meshtastic.Crypto;
11

12
/// <summary>
13
/// XEdDSA (Extended EdDSA) implementation for signing NodeInfo packets
14
/// Simplified implementation for demonstration and payload size analysis
15
/// </summary>
16
public static class XEdDSASigning
17
{
18
    /// <summary>
19
    /// Generate an Ed25519 key pair for signing (simplified approach)
20
    /// </summary>
21
    /// <returns>Tuple of (Ed25519 private key, Ed25519 public key)</returns>
22
    public static (byte[] edPrivateKey, byte[] edPublicKey) GenerateSigningKeyPair()
NEW
23
    {
×
NEW
24
        var keyPairGen = new Ed25519KeyPairGenerator();
×
NEW
25
        keyPairGen.Init(new KeyGenerationParameters(new SecureRandom(), 256));
×
26
        
NEW
27
        var keyPair = keyPairGen.GenerateKeyPair();
×
NEW
28
        var privateKey = ((Ed25519PrivateKeyParameters)keyPair.Private).GetEncoded();
×
NEW
29
        var publicKey = ((Ed25519PublicKeyParameters)keyPair.Public).GetEncoded();
×
30
        
NEW
31
        return (privateKey, publicKey);
×
NEW
32
    }
×
33

34
    /// <summary>
35
    /// Generate Ed25519 keys from an X25519 private key (simplified for demo)
36
    /// </summary>
37
    /// <param name="x25519PrivateKey">32-byte X25519 private key</param>
38
    /// <returns>Tuple of (Ed25519 private key, Ed25519 public key)</returns>
39
    public static (byte[] edPrivateKey, byte[] edPublicKey) GenerateEdDSAKeysFromX25519(byte[] x25519PrivateKey)
40
    {
10✔
41
        if (x25519PrivateKey.Length != 32)
10!
NEW
42
            throw new ArgumentException("X25519 private key must be 32 bytes", nameof(x25519PrivateKey));
×
43

44
        // For simplicity, use the X25519 key as seed for Ed25519 key generation
45
        // In a full XEdDSA implementation, this would use proper key derivation
46
        var hashedSeed = SHA256.HashData(x25519PrivateKey);
10✔
47
        
48
        var keyPairGen = new Ed25519KeyPairGenerator();
10✔
49
        var secureRandom = new SecureRandom();
10✔
50
        secureRandom.SetSeed(hashedSeed);
10✔
51
        keyPairGen.Init(new KeyGenerationParameters(secureRandom, 256));
10✔
52
        
53
        var keyPair = keyPairGen.GenerateKeyPair();
10✔
54
        // Proper XEdDSA key derivation with domain separation
55
        // Ed25519 seed = SHA-512("XEdDSA" || x25519PrivateKey)[0..31]
56
        byte[] domain = Encoding.ASCII.GetBytes("XEdDSA");
10✔
57
        byte[] input = new byte[domain.Length + x25519PrivateKey.Length];
10✔
58
        Buffer.BlockCopy(domain, 0, input, 0, domain.Length);
10✔
59
        Buffer.BlockCopy(x25519PrivateKey, 0, input, domain.Length, x25519PrivateKey.Length);
10✔
60
        byte[] hash = SHA512.HashData(input);
10✔
61
        byte[] ed25519Seed = new byte[32];
10✔
62
        Array.Copy(hash, 0, ed25519Seed, 0, 32);
10✔
63

64
        // Create Ed25519 private key from seed
65
        var edPrivateKeyParam = new Ed25519PrivateKeyParameters(ed25519Seed, 0);
10✔
66
        var edPublicKeyParam = edPrivateKeyParam.GeneratePublicKey();
10✔
67
        var privateKey = edPrivateKeyParam.GetEncoded();
10✔
68
        var publicKey = edPublicKeyParam.GetEncoded();
10✔
69
        return (privateKey, publicKey);
10✔
70
    }
10✔
71

72
    /// <summary>
73
    /// Convert X25519 public key to Ed25519 public key (simplified for demo)
74
    /// </summary>
75
    /// <param name="x25519PublicKey">32-byte X25519 public key</param>
76
    /// <returns>32-byte Ed25519 public key</returns>
77
    public static byte[] ConvertX25519PublicKeyToEd25519(byte[] x25519PublicKey)
78
    {
1✔
79
        if (x25519PublicKey.Length != 32)
1!
NEW
80
            throw new ArgumentException("X25519 public key must be 32 bytes", nameof(x25519PublicKey));
×
81

82
        // Simplified conversion - in practice would use proper birational map
83
        // For demo purposes, derive deterministic Ed25519 key from X25519 key
84
        var hashedKey = SHA256.HashData(x25519PublicKey);
1✔
85
        
86
        var keyPairGen = new Ed25519KeyPairGenerator();
1✔
87
        var secureRandom = new SecureRandom();
1✔
88
        secureRandom.SetSeed(hashedKey);
1✔
89
        keyPairGen.Init(new KeyGenerationParameters(secureRandom, 256));
1✔
90
        
91
        var keyPair = keyPairGen.GenerateKeyPair();
1✔
92
        var edPublicKey = ((Ed25519PublicKeyParameters)keyPair.Public).GetEncoded();
1✔
93
        
94
        // Clear the sign bit (bit 7 of the last byte) as per Ed25519 specification
95
        // Implements the birational map from Montgomery (X25519) u to Edwards (Ed25519) y:
96
        // y = (u - 1) / (u + 1) mod p
97
        // See: https://tools.ietf.org/html/rfc7748#section-5
98

99
        // Curve25519 prime: 2^255 - 19
100
        // Ed25519 field prime: 2^255 - 19
101
        BigInteger p = BigInteger.ValueOf(2).Pow(255).Subtract(BigInteger.ValueOf(19));
1✔
102

103
        // Interpret the X25519 public key as a little-endian integer u
104
        byte[] uBytes = new byte[32];
1✔
105
        Array.Copy(x25519PublicKey, uBytes, 32);
1✔
106
        // Ensure top bit is masked (Montgomery u-coordinate is 255 bits)
107
        uBytes[31] &= 0x7F;
1✔
108
        BigInteger u = new BigInteger(1, uBytes.Reverse().ToArray());
1✔
109

110
        // Compute y = (u - 1) * (u + 1)^-1 mod p
111
        BigInteger one = BigInteger.One;
1✔
112
        BigInteger uMinus1 = u.Subtract(one).Mod(p);
1✔
113
        BigInteger uPlus1 = u.Add(one).Mod(p);
1✔
114
        BigInteger uPlus1Inv = uPlus1.ModInverse(p);
1✔
115
        BigInteger y = uMinus1.Multiply(uPlus1Inv).Mod(p);
1✔
116

117
        // Encode y as 32-byte little-endian
118
        byte[] yBytes = y.ToByteArrayUnsigned();
1✔
119
        byte[] edPublicKeyResult = new byte[32];
1✔
120
        // Copy yBytes into edPublicKey (little-endian)
121
        for (int i = 0; i < yBytes.Length && i < 32; i++)
66✔
122
        {
32✔
123
            edPublicKeyResult[i] = yBytes[yBytes.Length - 1 - i];
32✔
124
        }
32✔
125
        // If yBytes is shorter than 32 bytes, the rest is already zero
126

127
        // Set the sign bit to 0 (positive x)
128
        edPublicKeyResult[31] &= 0x7F;
1✔
129
        return edPublicKeyResult;
1✔
130
    }
1✔
131

132
    /// <summary>
133
    /// Sign a message using Ed25519
134
    /// </summary>
135
    /// <param name="message">Message to sign</param>
136
    /// <param name="edPrivateKey">Ed25519 private key</param>
137
    /// <param name="useShortHash">Use SHA-256 instead of SHA-512 for hashing before signing</param>
138
    /// <returns>64-byte signature</returns>
139
    public static byte[] Sign(byte[] message, byte[] edPrivateKey, byte[] edPublicKey, bool useShortHash = true)
140
    {
8✔
141
        if (message == null) throw new ArgumentNullException(nameof(message));
9✔
142
        if (edPrivateKey.Length != 32) throw new ArgumentException("Ed25519 private key must be 32 bytes", nameof(edPrivateKey));
8✔
143

144
        // Hash the message using the selected algorithm
145
        var messageHash = useShortHash ? SHA256.HashData(message) : SHA512.HashData(message);
6✔
146

147
        // Create Ed25519 signer
148
        var signer = new Ed25519Signer();
6✔
149
        var privateKeyParams = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
6✔
150
        
151
        signer.Init(true, privateKeyParams);
6✔
152
        signer.BlockUpdate(messageHash, 0, messageHash.Length);
6✔
153
        
154
        return signer.GenerateSignature();
6✔
155
    }
6✔
156

157
    /// <summary>
158
    /// Verify an Ed25519 signature
159
    /// </summary>
160
    /// <param name="message">Original message</param>
161
    /// <param name="signature">64-byte signature</param>
162
    /// <param name="edPublicKey">Ed25519 public key of the signer</param>
163
    /// <param name="useShortHash">Use SHA-256 instead of SHA-512 for hashing</param>
164
    /// <returns>True if signature is valid</returns>
165
    public static bool Verify(byte[] message, byte[] signature, byte[] edPublicKey, bool useShortHash = true)
166
    {
6✔
167
        if (message == null) throw new ArgumentNullException(nameof(message));
6!
168
        if (signature == null || signature.Length != 64) throw new ArgumentException("Signature must be 64 bytes", nameof(signature));
6!
169
        if (edPublicKey == null || edPublicKey.Length != 32) throw new ArgumentException("Ed25519 public key must be 32 bytes", nameof(edPublicKey));
6!
170

171
        try
172
        {
6✔
173
            // Hash the message using the selected algorithm
174
            var messageHash = useShortHash ? SHA256.HashData(message) : SHA512.HashData(message);
6✔
175

176
            // Create Ed25519 verifier
177
            var verifier = new Ed25519Signer();
6✔
178
            var publicKeyParams = new Ed25519PublicKeyParameters(edPublicKey, 0);
6✔
179
            
180
            verifier.Init(false, publicKeyParams);
5✔
181
            verifier.BlockUpdate(messageHash, 0, messageHash.Length);
5✔
182
            
183
            return verifier.VerifySignature(signature);
5✔
184
        }
185
        catch
1✔
186
        {
1✔
187
            return false;
1✔
188
        }
189
    }
6✔
190

191
    /// <summary>
192
    /// Verify a signature using X25519 public key (for compatibility)
193
    /// </summary>
194
    /// <param name="message">Original message</param>
195
    /// <param name="signature">64-byte signature</param>
196
    /// <param name="x25519PublicKey">X25519 public key of the signer</param>
197
    /// <param name="useShortHash">Use SHA-256 instead of SHA-512 for verification</param>
198
    /// <returns>True if signature is valid</returns>
199
    public static bool VerifyWithX25519Key(byte[] message, byte[] signature, byte[] x25519PublicKey, bool useShortHash = true)
NEW
200
    {
×
201
        try
NEW
202
        {
×
203
            // Convert X25519 public key to Ed25519 public key
NEW
204
            var edPublicKey = ConvertX25519PublicKeyToEd25519(x25519PublicKey);
×
NEW
205
            return Verify(message, signature, edPublicKey, useShortHash);
×
206
        }
NEW
207
        catch
×
NEW
208
        {
×
NEW
209
            return false;
×
210
        }
NEW
211
    }
×
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc