Skip to content

Commit 14f302d

Browse files
feat: enhances tests for detached signature handling and conversion.
Refactors the signing and verification logic to default to detached signatures for improved security and efficiency. Adds functionality to manually convert non-detached signatures to detached signatures, ensuring compatibility and flexibility in signature management. Updates tests to reflect the new default behavior and conversion capabilities.
1 parent 781ec18 commit 14f302d

File tree

1 file changed

+90
-38
lines changed

1 file changed

+90
-38
lines changed

test/unit/detachedSignatures.test.ts

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ describe('Detached Signatures', () => {
1010
const keypair = crypto.generateKeypair()
1111
const message = 'Hello, World!'
1212
const messageHash = crypto.hash(message)
13-
13+
1414
// Create detached signature
1515
const signature = crypto.signDetached(messageHash, keypair.secretKey)
16-
16+
1717
// Verify signature is 64 bytes (128 hex chars)
1818
expect(signature.length).toBe(128)
19-
19+
2020
// Verify the signature
2121
const isValid = crypto.verifyDetached(messageHash, signature, keypair.publicKey)
2222
expect(isValid).toBe(true)
23-
23+
2424
// Verify with wrong message fails
2525
const wrongMessage = crypto.hash('Wrong message')
2626
const isInvalid = crypto.verifyDetached(wrongMessage, signature, keypair.publicKey)
@@ -34,19 +34,19 @@ describe('Detached Signatures', () => {
3434
const testObj = {
3535
message: 'Test object',
3636
value: 42,
37-
timestamp: Date.now()
37+
timestamp: Date.now(),
3838
}
39-
39+
4040
// Sign with detached signature
4141
const signedObj = crypto.signObjDetached(testObj, keypair.secretKey, keypair.publicKey)
42-
42+
4343
// Verify signature is 64 bytes
4444
expect(signedObj.sign.sig.length).toBe(128)
45-
45+
4646
// Verify the signed object
4747
const isValid = crypto.verifyObjDetached(signedObj)
4848
expect(isValid).toBe(true)
49-
49+
5050
// Tamper with object and verify it fails
5151
signedObj.value = 43
5252
const isInvalid = crypto.verifyObjDetached(signedObj)
@@ -59,51 +59,51 @@ describe('Detached Signatures', () => {
5959
const keypair = crypto.generateKeypair()
6060
const message = 'Test message'
6161
const messageHash = crypto.hash(message)
62-
62+
6363
// Create old-style (non-detached) signature
6464
const oldSignature = crypto.sign(messageHash, keypair.secretKey, false)
6565
// Old signature should be 96 bytes for 32-byte hash (64 + 32)
6666
expect(oldSignature.length).toBe(192)
67-
68-
// Create new-style (detached) signature
67+
68+
// Create new-style (detached) signature
6969
const newSignature = crypto.sign(messageHash, keypair.secretKey, true)
7070
// New signature should be 64 bytes
7171
expect(newSignature.length).toBe(128)
72-
72+
7373
// Both should verify successfully with verifyObj
7474
const testObj1 = { data: 'test' }
7575
const testObj2 = { data: 'test' }
76-
76+
7777
// Sign with old style
78-
crypto.signObj(testObj1, keypair.secretKey, keypair.publicKey, false)
79-
expect(crypto.verifyObj(testObj1)).toBe(true)
80-
78+
const signedObj1 = crypto.signObj(testObj1, keypair.secretKey, keypair.publicKey, false)
79+
expect(crypto.verifyObj(signedObj1)).toBe(true)
80+
8181
// Sign with new style
82-
crypto.signObj(testObj2, keypair.secretKey, keypair.publicKey, true)
83-
expect(crypto.verifyObj(testObj2)).toBe(true)
82+
const signedObj2 = crypto.signObj(testObj2, keypair.secretKey, keypair.publicKey, true)
83+
expect(crypto.verifyObj(signedObj2)).toBe(true)
8484
})
8585

8686
it('should handle mixed signature types correctly', () => {
8787
const keypair = crypto.generateKeypair()
8888
const testObj = { data: 'test data', id: 123 }
89-
89+
9090
// Create both types of signatures
9191
const objOld = JSON.parse(JSON.stringify(testObj))
9292
const objNew = JSON.parse(JSON.stringify(testObj))
93-
93+
9494
// Sign with old method
9595
crypto.signObj(objOld, keypair.secretKey, keypair.publicKey, false)
96-
97-
// Sign with new method
96+
97+
// Sign with new method
9898
crypto.signObj(objNew, keypair.secretKey, keypair.publicKey, true)
99-
99+
100100
// Old signature should be longer
101101
expect(objOld.sign.sig.length).toBeGreaterThan(objNew.sign.sig.length)
102-
102+
103103
// Both should verify with verifyObj (auto-detection)
104104
expect(crypto.verifyObj(objOld)).toBe(true)
105105
expect(crypto.verifyObj(objNew)).toBe(true)
106-
106+
107107
// New detached verify should work only with detached signature
108108
expect(crypto.verifyObjDetached(objNew)).toBe(true)
109109
expect(() => crypto.verifyObjDetached(objOld)).toThrow()
@@ -114,49 +114,101 @@ describe('Detached Signatures', () => {
114114
it('should produce different signature lengths based on detached parameter', () => {
115115
const keypair = crypto.generateKeypair()
116116
const message = crypto.hash('test')
117-
117+
118118
// Default (undefined) should now use detached
119119
const defaultSig = crypto.sign(message, keypair.secretKey)
120120
expect(defaultSig.length).toBe(128) // 64 bytes sig only
121-
121+
122122
// Explicit false should use non-detached
123123
const nonDetachedSig = crypto.sign(message, keypair.secretKey, false)
124124
expect(nonDetachedSig.length).toBe(192) // 96 bytes = 64 sig + 32 msg
125-
125+
126126
// Explicit true should use detached
127127
const detachedSig = crypto.sign(message, keypair.secretKey, true)
128128
expect(detachedSig.length).toBe(128) // 64 bytes sig only
129129
})
130130
})
131-
131+
132132
describe('Default behavior change', () => {
133133
it('sign() should default to detached signatures', () => {
134134
const keypair = crypto.generateKeypair()
135135
const message = crypto.hash('test message')
136-
136+
137137
// Call without detached parameter
138138
const signature = crypto.sign(message, keypair.secretKey)
139-
139+
140140
// Should produce 64-byte signature
141141
expect(signature.length).toBe(128) // 128 hex chars = 64 bytes
142-
142+
143143
// Should verify with verifyDetached
144144
expect(crypto.verifyDetached(message, signature, keypair.publicKey)).toBe(true)
145145
})
146-
146+
147147
it('signObj() should default to detached signatures', () => {
148148
const keypair = crypto.generateKeypair()
149149
const obj = { data: 'test', value: 123 }
150-
150+
151151
// Call without detached parameter
152152
const signedObj = crypto.signObj(obj, keypair.secretKey, keypair.publicKey)
153-
153+
154154
// Should produce 64-byte signature
155155
expect(signedObj.sign.sig.length).toBe(128)
156-
156+
157157
// Should verify with both methods due to auto-detection
158158
expect(crypto.verifyObj(signedObj)).toBe(true)
159159
expect(crypto.verifyObjDetached(signedObj)).toBe(true)
160160
})
161161
})
162-
})
162+
163+
describe('Manual signature conversion', () => {
164+
it('should convert non-detached signature to detached by removing excess and verify with verifyObj', () => {
165+
const keypair = crypto.generateKeypair()
166+
const testObj = {
167+
message: 'Test conversion',
168+
timestamp: Date.now(),
169+
id: 'test-123',
170+
}
171+
172+
// Create a copy for manual signature manipulation
173+
const objForManualConversion = JSON.parse(JSON.stringify(testObj))
174+
175+
// Sign with old method (non-detached) - this includes message + signature
176+
const signedObjOld = crypto.signObj(objForManualConversion, keypair.secretKey, keypair.publicKey, false)
177+
178+
// Verify the old signature works
179+
expect(crypto.verifyObj(signedObjOld)).toBe(true)
180+
181+
// Get the non-detached signature
182+
const nonDetachedSig = signedObjOld.sign.sig
183+
184+
// Non-detached signature should be longer than 128 hex chars (64 bytes)
185+
expect(nonDetachedSig.length).toBeGreaterThan(128)
186+
187+
// Manual conversion: Extract just the signature part (first 128 hex chars = 64 bytes)
188+
// In libsodium, non-detached signatures have format: [64-byte signature][original message]
189+
const detachedSigFromOld = nonDetachedSig.substring(0, 128)
190+
191+
// Verify the detached signature is exactly 64 bytes
192+
expect(detachedSigFromOld.length).toBe(128)
193+
194+
// Create a new object with the manually converted detached signature
195+
const objWithDetachedSig = {
196+
...testObj,
197+
sign: {
198+
owner: signedObjOld.sign.owner,
199+
sig: detachedSigFromOld,
200+
},
201+
}
202+
203+
// This should verify successfully using verifyObj with auto-detection
204+
expect(crypto.verifyObj(objWithDetachedSig)).toBe(true)
205+
206+
// It should also verify with the specific detached verification method
207+
expect(crypto.verifyObjDetached(objWithDetachedSig)).toBe(true)
208+
209+
// For additional verification, let's also test direct signature verification
210+
const objHash = crypto.hashObj(objWithDetachedSig, true) // true to remove sign field for hashing
211+
expect(crypto.verifyDetached(objHash, detachedSigFromOld, keypair.publicKey)).toBe(true)
212+
})
213+
})
214+
})

0 commit comments

Comments
 (0)