Skip to content

Commit 1ec3605

Browse files
committed
schnorr sig spec, signing via node
1 parent 5476306 commit 1ec3605

3 files changed

Lines changed: 618 additions & 101 deletions

File tree

SCHNORR_SIGNATURE_SPEC.md

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
# Schnorr Signature Specification for Basis Tracker
2+
3+
## Overview
4+
5+
This specification defines the Schnorr signature algorithm implementation for the Basis Tracker system. It follows the chaincash-rs approach with secp256k1 elliptic curve cryptography and is designed to be compatible with Ergo blockchain requirements.
6+
7+
## Signature Format
8+
9+
### Public Keys
10+
- **Format**: Compressed secp256k1 public keys
11+
- **Size**: 33 bytes total
12+
- **Structure**:
13+
- 1-byte prefix (0x02 or 0x03) indicating compressed format
14+
- 32-byte x-coordinate of the elliptic curve point
15+
- **Encoding**: Hexadecimal representation (66 characters)
16+
17+
### Signatures
18+
- **Format**: 65-byte Schnorr signatures following chaincash-rs format
19+
- **Size**: 65 bytes total (130 hex characters when encoded)
20+
- **Structure**:
21+
- 1-byte prefix (0x02 or 0x03) - compressed public key format indicator
22+
- 33-byte 'a' component (32-byte random point + 1-byte prefix)
23+
- 32-byte 'z' component (response value)
24+
- **Total**: 1 + 33 + 32 = 65 bytes
25+
26+
### Example Signature Breakdown
27+
For signature `"02f40cf9d43542868b3e97a790872812574a8be92fd02ce229908d578724c28b925fa689420f9be9f5ddb3d22a6b2a317351008ad38fe222f66aae251f04daae03"`:
28+
- `02`: Prefix (compressed public key format)
29+
- `f40cf9d43542868b3e97a790872812574a8be92fd02ce229908d578724c28b92`: 'a' component (33 bytes)
30+
- `5fa689420f9be9f5ddb3d22a6b2a317351008ad38fe222f66aae251f04daae03`: 'z' component (32 bytes)
31+
32+
## Signing Process
33+
34+
### Message Format
35+
The message to be signed follows the format: `recipient_pubkey || amount_be_bytes || timestamp_be_bytes`
36+
37+
Where:
38+
- `recipient_pubkey`: 33-byte compressed public key of the recipient (hex-encoded)
39+
- `amount_be_bytes`: 8-byte big-endian representation of the amount
40+
- `timestamp_be_bytes`: 8-byte big-endian representation of the Unix timestamp
41+
42+
### Signing Algorithm
43+
1. **Input Validation**:
44+
- Verify recipient public key is 33 bytes in compressed format
45+
- Verify amount and timestamp are valid u64 values
46+
47+
2. **Message Construction**:
48+
- Concatenate recipient public key bytes (33 bytes)
49+
- Concatenate amount as 8-byte big-endian (8 bytes)
50+
- Concatenate timestamp as 8-byte big-endian (8 bytes)
51+
- Total message length: 49 bytes
52+
53+
3. **Nonce Generation**:
54+
- Generate a cryptographically secure random nonce `k` (scalar value)
55+
- Ensure `k` is within the secp256k1 field range
56+
57+
4. **Random Point Calculation**:
58+
- Compute `R = k * G` where `G` is the secp256k1 generator point
59+
- Convert `R` to compressed format (33 bytes with 0x02/0x03 prefix)
60+
- This becomes the 'a' component of the signature
61+
62+
5. **Challenge Computation**:
63+
- Compute `e = H(R || message || public_key)` using Blake2b512
64+
- Reduce `e` modulo the secp256k1 order `n` to get scalar
65+
66+
6. **Response Calculation**:
67+
- Compute `z = k + e * s (mod n)` where `s` is the private key
68+
- This becomes the 'z' component of the signature
69+
70+
7. **Signature Assembly**:
71+
- Combine prefix (from compressed R), 'a' component (R), and 'z' component
72+
- Total signature: 1 + 33 + 32 = 65 bytes
73+
74+
### Reference Implementation (Pseudocode)
75+
```
76+
function schnorr_sign(message_bytes, private_key_scalar, public_key_bytes):
77+
// Generate random nonce
78+
k = random_scalar()
79+
80+
// Calculate random point R = k*G
81+
R_point = multiply_generator(k)
82+
R_compressed = compress_point(R_point) // 33 bytes with 0x02/0x03 prefix
83+
84+
// Calculate challenge e = H(R || message || public_key)
85+
challenge_input = R_compressed || message_bytes || public_key_bytes
86+
e_full = blake2b512(challenge_input)
87+
e = reduce_mod_n(e_full) // Reduce to field range
88+
89+
// Calculate response z = k + e*s (mod n)
90+
z = (k + e * private_key_scalar) % curve_order_n
91+
92+
// Assemble signature: [prefix_byte || R_compressed_without_prefix || z_bytes]
93+
signature = [R_compressed[0]] || R_compressed[1:] || int_to_bytes(z, 32)
94+
95+
return signature // 65 bytes total
96+
```
97+
98+
## Verification Process
99+
100+
### Verification Algorithm
101+
1. **Signature Parsing**:
102+
- Extract prefix byte (0x02 or 0x03)
103+
- Extract 'a' component (33 bytes - compressed point A)
104+
- Extract 'z' component (32 bytes - response z)
105+
106+
2. **Input Validation**:
107+
- Verify signature is exactly 65 bytes
108+
- Verify prefix is 0x02 or 0x03
109+
- Verify 'a' component represents a valid point on secp256k1 curve
110+
- Verify 'z' component is within field range
111+
112+
3. **Challenge Recomputation**:
113+
- Compute `e = H(A || message || public_key)` using Blake2b512
114+
- Reduce `e` modulo the secp256k1 order `n`
115+
116+
4. **Verification Equation**:
117+
- Verify that `g^z = A * x^e` where:
118+
- `g` is the secp256k1 generator point
119+
- `z` is the response from signature
120+
- `A` is the random point from signature
121+
- `x` is the public key point
122+
- `e` is the challenge
123+
124+
5. **Alternative Verification**:
125+
- Compute `R_check = z*G - e*X` where `X` is the public key point
126+
- Verify that `compress_point(R_check)` equals the 'a' component from signature
127+
128+
### Reference Implementation (Pseudocode)
129+
```
130+
function schnorr_verify(signature, message_bytes, public_key_bytes):
131+
if len(signature) != 65:
132+
return false
133+
134+
prefix = signature[0]
135+
a_component = signature[1:34] // 33 bytes
136+
z_component = signature[34:66] // 32 bytes
137+
138+
// Validate prefix
139+
if prefix != 0x02 and prefix != 0x03:
140+
return false
141+
142+
// Parse z as scalar
143+
z = bytes_to_scalar(z_component)
144+
145+
// Parse A (the 'a' component) as a point
146+
A_bytes = [prefix] + a_component[1:] // Reconstruct with prefix
147+
A_point = decompress_point(A_bytes)
148+
if A_point is invalid:
149+
return false
150+
151+
// Parse public key
152+
X_point = decompress_point(public_key_bytes)
153+
if X_point is invalid:
154+
return false
155+
156+
// Recompute challenge
157+
challenge_input = A_bytes || message_bytes || public_key_bytes
158+
e_full = blake2b512(challenge_input)
159+
e = reduce_mod_n(e_full)
160+
161+
// Verify g^z = A * x^e by checking if z*G = A + e*X
162+
left_side = multiply_generator(z)
163+
right_side = A_point + multiply_point(X_point, e)
164+
165+
return left_side == right_side
166+
```
167+
168+
## Cryptographic Primitives
169+
170+
### Hash Function
171+
- **Algorithm**: Blake2b-512
172+
- **Output**: 64-byte hash
173+
- **Usage**: Challenge computation in Schnorr signature scheme
174+
- **Security**: Collision resistance, preimage resistance
175+
176+
### Elliptic Curve
177+
- **Curve**: secp256k1
178+
- **Field**: Prime field with p = 2^256 - 2^32 - 977
179+
- **Generator**: Standard secp256k1 generator point G
180+
- **Order**: Curve order n ≈ 2^256 - 4.3×10^67
181+
182+
### Field Operations
183+
- **Modular Arithmetic**: Operations modulo the secp256k1 curve order n
184+
- **Scalar Multiplication**: Efficient point multiplication k*P
185+
- **Point Addition**: Elliptic curve point addition
186+
187+
## Security Considerations
188+
189+
### Nonce Security
190+
- Nonces must be cryptographically secure random values
191+
- Never reuse nonces for different messages
192+
- Consider deterministic nonce generation (RFC 6979) to prevent nonce reuse attacks
193+
194+
### Side-Channel Resistance
195+
- Implement constant-time operations where possible
196+
- Protect against timing attacks during scalar multiplication
197+
- Secure handling of private key material
198+
199+
### Validation Requirements
200+
- Always validate public keys are on the correct curve
201+
- Verify signature components are within proper ranges
202+
- Reject signatures with invalid point encodings
203+
204+
## API Integration
205+
206+
### Ergo Node API Endpoint
207+
- **Path**: `/utils/schnorrSign`
208+
- **Method**: POST
209+
- **Content-Type**: application/json
210+
- **Authentication**: API key in header
211+
212+
### Request Format
213+
```json
214+
{
215+
"address": "String",
216+
"message": "String"
217+
}
218+
```
219+
220+
### Request Fields
221+
- `address`: String - The Ergo address (P2PK) for which to generate the signature
222+
- `message`: String - Hex-encoded message to be signed (arbitrary bytes)
223+
224+
### Response Format (Success)
225+
```json
226+
{
227+
"signedMessage": "String",
228+
"signature": "String",
229+
"publicKey": "String"
230+
}
231+
```
232+
233+
### Response Fields
234+
- `signedMessage`: String - The original hex-encoded message that was signed
235+
- `signature`: String - 65-byte Schnorr signature in hex format (130 characters)
236+
- `publicKey`: String - The public key corresponding to the private key used for signing (33 bytes in hex, 66 characters)
237+
238+
### Error Response
239+
```json
240+
{
241+
"error": {
242+
"code": "String",
243+
"message": "String"
244+
}
245+
}
246+
```
247+
248+
## Test Vectors
249+
250+
### Example Message Construction
251+
Given:
252+
- Recipient pubkey: `02d1b60084a5af8dc3e006802a36dddfd09684eaf90164a5ad978b6e9b97eb328b` (33 bytes)
253+
- Amount: 1000000000 (0x000000003B9ACA00)
254+
- Timestamp: 1672531200 (0x63B1A800)
255+
256+
Message bytes: `02d1b60084a5af8dc3e006802a36dddfd09684eaf90164a5ad978b6e9b97eb328b000000003B9ACA000000000063B1A800`
257+
258+
### Expected Signature Format
259+
- Length: 65 bytes (130 hex characters)
260+
- Structure: [1-byte prefix][33-byte A component][32-byte z component]
261+
- Valid prefix: 0x02 or 0x03
262+
263+
## Compliance Requirements
264+
265+
### Chaincash-rs Compatibility
266+
- Follow the same signature format as chaincash-rs library
267+
- Maintain compatibility with existing Basis Tracker implementations
268+
- Use the same message construction format
269+
270+
### Ergo Blockchain Compatibility
271+
- Signatures must be verifiable by Ergo's cryptographic primitives
272+
- Public keys must be in compressed format expected by Ergo
273+
- Follow Ergo's Schnorr signature verification procedures
274+
275+
## Implementation Guidelines
276+
277+
### Recommended Libraries
278+
- **secp256k1**: For elliptic curve operations
279+
- **blake2**: For hash function implementation
280+
- **libsodium**: For additional cryptographic primitives (optional)
281+
282+
### Performance Considerations
283+
- Optimize scalar multiplication using precomputed tables
284+
- Consider batch verification for multiple signatures
285+
- Efficient point compression/decompression routines
286+
287+
### Error Handling
288+
- Proper validation of all inputs
289+
- Clear error messages for invalid signatures
290+
- Secure handling of cryptographic failures

0 commit comments

Comments
 (0)