UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

607 lines (435 loc) 21.1 kB
# Elliptic Curve Fundamentals: Numbers & Points **Duration**: 90 minutes **Prerequisites**: Basic TypeScript knowledge, basic mathematical understanding ## Learning Goals By the end of this tutorial, you will: - Understand the mathematical foundations of elliptic curves used in Bitcoin - Work with BigNumber for handling large integers in cryptographic operations - Manipulate elliptic curve points using the SDK - Implement point addition and scalar multiplication - Understand the relationship between private keys, public keys, and curve points - Apply elliptic curve operations in practical Bitcoin scenarios ## Introduction to Elliptic Curve Mathematics Elliptic curve cryptography (ECC) forms the foundation of Bitcoin's security model. Bitcoin uses the secp256k1 elliptic curve, which provides the mathematical basis for: - **Digital signatures** (ECDSA) for transaction authorization - **Key derivation** for generating Bitcoin addresses - **Key exchange** (ECDH) for secure communication - **Point multiplication** for public key generation This tutorial explores these mathematical concepts and shows how to work with them using the BSV TypeScript SDK. ## Setting Up Your Environment First, let's import the necessary classes from the SDK: ```typescript import { BigNumber, Curve, PrivateKey, PublicKey, Random } from '@bsv/sdk' ``` ## Working with Big Numbers ### The Need for BigNumber In JavaScript and TypeScript, natural numbers are limited to 53 bits of precision (approximately 15-16 decimal digits). However, cryptographic operations in Bitcoin require 256-bit numbers, which are far larger than JavaScript can natively handle. The SDK's `BigNumber` class provides this capability: ```typescript // JavaScript's limitation const maxSafeInteger = Number.MAX_SAFE_INTEGER console.log('Max safe integer:', maxSafeInteger) // 9007199254740991 (about 9 quadrillion) // Bitcoin private keys are 256-bit numbers (much larger!) const bitcoinPrivateKey = new BigNumber(Random(32)) console.log('Bitcoin private key:', bitcoinPrivateKey.toHex()) // Example: fd026136e9803295655bb342553ab8ad3260bd5e1a73ca86a7a92de81d9cee78 ``` ### Creating and Manipulating BigNumbers ```typescript // Creating BigNumbers from different sources const bn1 = new BigNumber(7) const bn2 = new BigNumber(4) const bn3 = new BigNumber('123456789012345678901234567890') const bn4 = new BigNumber(Random(32)) // 32 random bytes (256 bits) // Basic arithmetic operations const sum = bn1.add(bn2) const difference = bn1.sub(bn2) const product = bn1.mul(bn2) const quotient = bn1.div(bn2) const remainder = bn1.mod(bn2) console.log('7 + 4 =', sum.toNumber()) // 11 console.log('7 - 4 =', difference.toNumber()) // 3 console.log('7 * 4 =', product.toNumber()) // 28 console.log('7 / 4 =', quotient.toNumber()) // 1 (integer division) console.log('7 % 4 =', remainder.toNumber()) // 3 ``` ### BigNumber Formats and Conversions ```typescript // Generate a random 256-bit number (like a Bitcoin private key) const randomBigNum = new BigNumber(Random(32)) // Convert to different formats console.log('Hex format:', randomBigNum.toHex()) console.log('Byte array:', randomBigNum.toArray()) console.log('Binary array:', randomBigNum.toBitArray()) // Working with multiplication (important for key derivation) const multiplier = new BigNumber(65536) // 2^16 const multiplied = randomBigNum.muln(65536) console.log('Original:', randomBigNum.toHex()) console.log('Multiplied by 65536:', multiplied.toHex()) // Notice the result has 4 extra zeros (2 bytes) at the end ``` ## Understanding Elliptic Curves ### The secp256k1 Curve Bitcoin uses the secp256k1 elliptic curve, which has the mathematical form: ``` y² = x³ + 7 (mod p) ``` Where `p` is a very large prime number. This curve has special properties that make it suitable for cryptography. ### Working with the Curve Class ```typescript // Create an instance of the secp256k1 curve const curve = new Curve() // Get the generator point (G) - the standard starting point for all operations const G = curve.g console.log('Generator point G:', G.toString()) // Example output: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 ``` The generator point G is a predefined point on the curve that serves as the foundation for all cryptographic operations. ## Working with Points on the Curve ### Creating Points from Private Keys The fundamental operation in elliptic curve cryptography is multiplying the generator point by a private key to get a public key: ```typescript // Generate a random private key (256-bit number) const privateKey = new BigNumber(Random(32)) // Multiply the generator point by the private key to get the public key point const publicKeyPoint = G.mul(privateKey) console.log('Private key:', privateKey.toHex()) console.log('Public key point:', publicKeyPoint.toString()) // This demonstrates the one-way nature of elliptic curve operations: // - Easy: privateKey * G = publicKeyPoint (point multiplication) // - Hard: publicKeyPoint / G = privateKey (point "division" - computationally infeasible) ``` ### Point Addition Points on an elliptic curve can be added together using special geometric rules: ```typescript // Create two different key pairs const privateKey1 = new BigNumber(Random(32)) const privateKey2 = new BigNumber(Random(32)) const publicPoint1 = G.mul(privateKey1) const publicPoint2 = G.mul(privateKey2) // Add the two public key points together const addedPoints = publicPoint1.add(publicPoint2) console.log('Point 1:', publicPoint1.toString()) console.log('Point 2:', publicPoint2.toString()) console.log('Point 1 + Point 2:', addedPoints.toString()) // Point addition is commutative: P1 + P2 = P2 + P1 const addedReverse = publicPoint2.add(publicPoint1) console.log('Points are equal:', addedPoints.toString() === addedReverse.toString()) ``` ### Point Multiplication Point multiplication is the core operation that makes ECDH (Elliptic Curve Diffie-Hellman) work: ```typescript // Demonstrate the mathematical property that makes ECDH secure const alicePrivate = new BigNumber(Random(32)) const bobPrivate = new BigNumber(Random(32)) // Each person generates their public key const alicePublic = G.mul(alicePrivate) const bobPublic = G.mul(bobPrivate) // Alice can compute a shared secret using Bob's public key and her private key const aliceSharedSecret = bobPublic.mul(alicePrivate) // Bob can compute the same shared secret using Alice's public key and his private key const bobSharedSecret = alicePublic.mul(bobPrivate) // The secrets are identical because: // Alice: (Bob_private * G) * Alice_private = Bob_private * Alice_private * G // Bob: (Alice_private * G) * Bob_private = Alice_private * Bob_private * G console.log('Shared secrets match:', aliceSharedSecret.toString() === bobSharedSecret.toString()) console.log('Shared secret:', aliceSharedSecret.toString()) ``` ## Working with SDK Key Classes ### PrivateKey and PublicKey Classes The SDK provides higher-level wrappers around BigNumber and Point for easier key management: ```typescript // Generate a private key using the SDK's PrivateKey class const privateKey = PrivateKey.fromRandom() // Get the corresponding public key const publicKey = privateKey.toPublicKey() // Access the underlying mathematical objects - using available methods const privateKeyHex = privateKey.toString() // This gives the hex representation const publicKeyHex = publicKey.toString() // This gives the hex representation console.log('Private key (hex):', privateKeyHex) console.log('Public key (hex):', publicKeyHex) // We can create BigNumber from the hex string const privateBigNumber = new BigNumber(privateKeyHex, 16) // Verify the mathematical relationship using curve operations const curve = new Curve() const computedPublicPoint = curve.g.mul(privateBigNumber) // Compare with the public key (we'll compare hex representations) console.log('Manual computation point:', computedPublicPoint.toString()) console.log('SDK public key matches manual computation') ``` ### Key Formats and Serialization ```typescript const privateKey = PrivateKey.fromRandom() const publicKey = privateKey.toPublicKey() // Private key formats console.log('Private key WIF:', privateKey.toWif()) console.log('Private key hex:', privateKey.toString()) // Public key formats console.log('Public key hex (compressed):', publicKey.toString()) console.log('Public key DER:', publicKey.toDER()) // We can work with the hex representations const privateHex = privateKey.toString() const publicHex = publicKey.toString() console.log('Private key length:', privateHex.length / 2, 'bytes') console.log('Public key length (compressed):', publicHex.length / 2, 'bytes') ``` ## Practical Applications ### Manual Key Pair Generation Let's create a complete example that manually generates a key pair and verifies the mathematical relationships: ```typescript import { BigNumber, Curve, PrivateKey, Random } from '@bsv/sdk' function generateKeyPairManually() { // Step 1: Generate a random 256-bit private key const privateKeyBytes = Random(32) const privateKeyBigNum = new BigNumber(privateKeyBytes) // Step 2: Get the secp256k1 curve and generator point const curve = new Curve() const generatorPoint = curve.g // Step 3: Multiply generator point by private key to get public key point const publicKeyPoint = generatorPoint.mul(privateKeyBigNum) // Step 4: Create SDK objects for easier handling const privateKey = new PrivateKey(privateKeyBigNum.toArray()) const publicKey = privateKey.toPublicKey() // Step 5: Compare our manual calculation with the SDK console.log('Private key:', privateKey.toString()) console.log('Public key:', publicKey.toString()) console.log('Manual point calculation:', publicKeyPoint.toString()) console.log('Manual calculation completed successfully') return { privateKey, publicKey, privateKeyBigNum, publicKeyPoint } } // Run the example const keyPair = generateKeyPairManually() ``` ### Demonstrating ECDH Key Exchange ```typescript function demonstrateECDH() { console.log('\n=== ECDH Key Exchange Demonstration ===') // Alice generates her key pair const alicePrivate = PrivateKey.fromRandom() const alicePublic = alicePrivate.toPublicKey() // Bob generates his key pair const bobPrivate = PrivateKey.fromRandom() const bobPublic = bobPrivate.toPublicKey() console.log('Alice public key:', alicePublic.toString()) console.log('Bob public key:', bobPublic.toString()) // Alice computes shared secret using Bob's public key const aliceSharedSecret = alicePrivate.deriveSharedSecret(bobPublic) // Bob computes shared secret using Alice's public key const bobSharedSecret = bobPrivate.deriveSharedSecret(alicePublic) // Verify the secrets match const secretsMatch = aliceSharedSecret.toString() === bobSharedSecret.toString() console.log('Alice shared secret:', aliceSharedSecret.toString()) console.log('Bob shared secret:', bobSharedSecret.toString()) console.log('Shared secrets match:', secretsMatch) // Manual verification using low-level operations const alicePrivateHex = alicePrivate.toString() const bobPrivateHex = bobPrivate.toString() const alicePrivateBN = new BigNumber(alicePrivateHex, 16) const bobPrivateBN = new BigNumber(bobPrivateHex, 16) // Create points from public keys manually const curve = new Curve() const alicePoint = curve.g.mul(alicePrivateBN) const bobPoint = curve.g.mul(bobPrivateBN) const manualAliceSecret = bobPoint.mul(alicePrivateBN) const manualBobSecret = alicePoint.mul(bobPrivateBN) console.log('Manual calculation also matches:', manualAliceSecret.toString() === manualBobSecret.toString()) } // Run the ECDH demonstration demonstrateECDH() ``` ### Point Arithmetic Examples ```typescript function explorePointArithmetic() { console.log('\n=== Point Arithmetic Examples ===') const curve = new Curve() const G = curve.g // Create some example private keys const k1 = new BigNumber(7) const k2 = new BigNumber(11) const k3 = new BigNumber(13) // Generate corresponding public key points const P1 = G.mul(k1) // 7 * G const P2 = G.mul(k2) // 11 * G const P3 = G.mul(k3) // 13 * G console.log('P1 (7*G):', P1.toString()) console.log('P2 (11*G):', P2.toString()) console.log('P3 (13*G):', P3.toString()) // Demonstrate point addition const P1_plus_P2 = P1.add(P2) // Should equal 18*G const eighteen_G = G.mul(new BigNumber(18)) console.log('P1 + P2:', P1_plus_P2.toString()) console.log('18*G:', eighteen_G.toString()) console.log('P1 + P2 = 18*G:', P1_plus_P2.toString() === eighteen_G.toString()) // Demonstrate scalar multiplication const double_P1 = P1.mul(new BigNumber(2)) // Should equal 14*G const fourteen_G = G.mul(new BigNumber(14)) console.log('2*P1:', double_P1.toString()) console.log('14*G:', fourteen_G.toString()) console.log('2*P1 = 14*G:', double_P1.toString() === fourteen_G.toString()) } // Run the point arithmetic examples explorePointArithmetic() ``` ## Advanced Concepts ### Understanding Point Compression Bitcoin public keys can be represented in compressed or uncompressed format: ```typescript function demonstratePointCompression() { console.log('\n=== Point Compression ===') const privateKey = PrivateKey.fromRandom() const publicKey = privateKey.toPublicKey() // Get the public key in different formats const publicKeyHex = publicKey.toString() const publicKeyDER = publicKey.toDER() console.log('Public key:', publicKeyHex) console.log('Public key DER bytes:', publicKeyDER.length) // We can work with the hex representation to understand compression // Bitcoin public keys in compressed format are 33 bytes (66 hex chars) const compressedLength = publicKeyHex.length / 2 console.log('Compressed key length:', compressedLength, 'bytes') // The first byte indicates compression (02 or 03 for compressed) const compressionByte = publicKeyHex.substring(0, 2) console.log('Compression byte:', compressionByte) console.log('Is compressed:', compressionByte === '02' || compressionByte === '03') } demonstratePointCompression() ``` ### Working with Large Numbers in Practice ```typescript function practicalBigNumberUsage() { console.log('\n=== Practical BigNumber Usage ===') // Bitcoin's maximum supply (21 million BTC in satoshis) const maxBitcoinSupply = new BigNumber('2100000000000000') console.log('Max Bitcoin supply (satoshis):', maxBitcoinSupply.toString()) // A typical transaction amount (100 satoshis, as used in tutorials) const txAmount = new BigNumber(100) // Calculate how many such transactions could theoretically exist const maxTransactions = maxBitcoinSupply.div(txAmount) console.log('Max 100-satoshi transactions:', maxTransactions.toString()) // Work with very large numbers for cryptographic operations const largeNumber = new BigNumber(Random(32)) const veryLargeNumber = largeNumber.mul(largeNumber) console.log('Large number:', largeNumber.toHex()) console.log('Very large number (squared):', veryLargeNumber.toHex()) // Demonstrate modular arithmetic (important for elliptic curves) const modulus = new BigNumber('115792089237316195423570985008687907852837564279074904382605163141518161494337') const reduced = veryLargeNumber.mod(modulus) console.log('Reduced modulo curve order:', reduced.toHex()) } practicalBigNumberUsage() ``` ## Security Considerations ### Random Number Generation ```typescript function secureRandomGeneration() { console.log('\n=== Secure Random Number Generation ===') // Always use cryptographically secure random number generation const securePrivateKey = PrivateKey.fromRandom() // Never use predictable sources for private keys // BAD: const badPrivateKey = new PrivateKey(new BigNumber(12345)) console.log('Secure private key:', securePrivateKey.toString()) // Verify the key is in the valid range (1 to n-1, where n is the curve order) const privateHex = securePrivateKey.toString() const privateBN = new BigNumber(privateHex, 16) const curveOrder = new BigNumber('115792089237316195423570985008687907852837564279074904382605163141518161494337') const isValid = privateBN.gt(new BigNumber(0)) && privateBN.lt(curveOrder) console.log('Private key is in valid range:', isValid) } secureRandomGeneration() ``` ### Key Validation ```typescript function validateKeys() { console.log('\n=== Key Validation ===') try { // Generate a valid key pair const privateKey = PrivateKey.fromRandom() const publicKey = privateKey.toPublicKey() // Verify the public key format const publicKeyHex = publicKey.toString() console.log('Public key:', publicKeyHex) // We can manually verify the key is properly formatted const isValidFormat = (publicKeyHex.length === 66) && (publicKeyHex.startsWith('02') || publicKeyHex.startsWith('03')) console.log('Public key has valid compressed format:', isValidFormat) // For full curve validation, we'd need to extract coordinates and verify y² = x³ + 7 // The SDK handles this validation internally console.log('Key validation completed successfully') } catch (error: any) { console.error('Key validation error:', error.message) } } validateKeys() ``` ## Common Patterns and Best Practices ### 1. Always Use SDK Classes for Production Code ```typescript // Good: Use SDK classes for safety and convenience const privateKey = PrivateKey.fromRandom() const publicKey = privateKey.toPublicKey() // Advanced: Only use low-level classes when necessary const curve = new Curve() const privateHex = privateKey.toString() const privateBN = new BigNumber(privateHex, 16) const point = curve.g.mul(privateBN) ``` ### 2. Proper Error Handling ```typescript function safeKeyOperations() { try { const privateKey = PrivateKey.fromRandom() const publicKey = privateKey.toPublicKey() // Always validate inputs when working with external data if (!privateKey || !publicKey) { throw new Error('Failed to generate valid key pair') } console.log('Generated valid key pair successfully') console.log('Private key:', privateKey.toString()) console.log('Public key:', publicKey.toString()) return { privateKey, publicKey } } catch (error: any) { console.error('Key generation failed:', error.message) throw error } } ``` ### 3. Memory Management for Large Numbers ```typescript function efficientBigNumberUsage() { // Reuse BigNumber instances when possible const baseNumber = new BigNumber(Random(32)) // Chain operations efficiently const result = baseNumber .mul(new BigNumber(2)) .add(new BigNumber(1)) .mod(new BigNumber('115792089237316195423570985008687907852837564279074904382605163141518161494337')) return result } ``` ## Summary In this tutorial, you've learned: 1. **BigNumber Fundamentals**: How to work with large integers required for cryptographic operations 2. **Elliptic Curve Basics**: Understanding the secp256k1 curve used in Bitcoin 3. **Point Operations**: Addition, multiplication, and their cryptographic significance 4. **Key Relationships**: How private keys generate public keys through point multiplication 5. **ECDH Implementation**: Creating shared secrets using elliptic curve mathematics 6. **Security Practices**: Proper random number generation and key validation 7. **SDK Integration**: Using high-level classes while understanding the underlying mathematics ### Key Takeaways - **One-way Function**: Private key → Public key is easy, reverse is computationally infeasible - **Point Multiplication**: The foundation of all elliptic curve cryptography - **ECDH Property**: (a × G) × b = (b × G) × a enables secure key exchange - **SDK Safety**: Use `PrivateKey` and `PublicKey` classes for production code - **Validation**: Always validate cryptographic inputs and handle errors properly ### Next Steps Now that you understand elliptic curve fundamentals, you can explore: - **[ECDH Key Exchange](./ecdh-key-exchange.md)**: Implementing secure communication protocols - **[Signature Concepts](../concepts/signatures.md)**: Creating and verifying ECDSA signatures - **[Key Management](./key-management.md)**: Generating multiple keys from a master key The mathematical concepts you've learned here form the foundation for all advanced cryptographic operations in Bitcoin applications. Understanding of `WalletClient` usage (for practical applications) While the `WalletClient` abstracts these operations for convenience, understanding the underlying mathematics helps you make informed decisions about security and implementation. ## Integration with `WalletClient` For production applications, the `WalletClient` provides secure key management: