Skip to content

Commit 00e7521

Browse files
authored
Added FVM eip-191 support (#44)
* add fvm eip-191 command * update deps * add raw sig * remove magic numbers * code refurbish * format files
1 parent 5bd15af commit 00e7521

File tree

6 files changed

+452
-102
lines changed

6 files changed

+452
-102
lines changed

app.go

Lines changed: 128 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@
1717
package ledger_filecoin_go
1818

1919
import (
20+
"encoding/binary"
2021
"fmt"
2122

2223
ledger_go "github.com/zondax/ledger-go"
2324
)
2425

2526
func (sa *SignatureAnswer) SignatureBytes() []byte {
26-
out := make([]byte, 65)
27-
copy(out[:32], sa.r)
28-
copy(out[32:64], sa.s)
29-
out[64] = sa.v
27+
out := make([]byte, signatureLength)
28+
copy(out[signatureROffset:signatureROffset+signatureRLength], sa.r)
29+
copy(out[signatureSOffset:signatureSOffset+signatureSLength], sa.s)
30+
out[signatureVOffset] = sa.v
3031
return out
3132
}
3233

@@ -96,7 +97,7 @@ func FindLedgerFilecoinApp() (*LedgerFilecoin, error) {
9697
if err != nil {
9798
defer ledgerAPI.Close()
9899
if err.Error() == "[APDU_CODE_CLA_NOT_SUPPORTED] Class not supported" {
99-
return nil, fmt.Errorf("are you sure the Filecoin app is open?")
100+
return nil, fmt.Errorf("Filecoin app is not open on the device")
100101
}
101102
return nil, err
102103
}
@@ -117,20 +118,20 @@ func (ledger *LedgerFilecoin) Close() error {
117118

118119
// VersionIsSupported returns true if the App version is supported by this library
119120
func (ledger *LedgerFilecoin) CheckVersion(ver VersionInfo) error {
120-
return CheckVersion(ver, VersionInfo{0, 0, 3, 0})
121+
return CheckVersion(ver, VersionInfo{minVersionMode, minVersionMajor, minVersionMinor, minVersionPatch})
121122
}
122123

123124
// GetVersion returns the current version of the Filecoin user app
124125
func (ledger *LedgerFilecoin) GetVersion() (*VersionInfo, error) {
125-
message := []byte{CLA, INSGetVersion, 0, 0, 0}
126+
message := []byte{CLA, INSGetVersion, apduP1Default, apduP2Default, 0}
126127
response, err := ledger.api.Exchange(message)
127128

128129
if err != nil {
129130
return nil, err
130131
}
131132

132-
if len(response) < 4 {
133-
return nil, fmt.Errorf("invalid response")
133+
if len(response) < minVersionResponseLength {
134+
return nil, fmt.Errorf("invalid version response length: expected %d, got %d", minVersionResponseLength, len(response))
134135
}
135136

136137
ledger.version = VersionInfo{
@@ -156,18 +157,7 @@ func (ledger *LedgerFilecoin) Sign(bip44Path []uint32, transaction []byte, curve
156157
return nil, err
157158
}
158159

159-
// R,S,V and at least 1 bytes of the der sig
160-
if len(signatureBytes) < 66 {
161-
return nil, fmt.Errorf("The signature provided is too short.")
162-
}
163-
164-
signatureAnswer := SignatureAnswer{
165-
signatureBytes[0:32],
166-
signatureBytes[32:64],
167-
signatureBytes[64],
168-
signatureBytes[65:]}
169-
170-
return &signatureAnswer, nil
160+
return parseSignatureResponse(signatureBytes)
171161
}
172162

173163
// Deprecated: Use GetPublicKey instead.
@@ -206,75 +196,150 @@ func (ledger *LedgerFilecoin) ShowAddressPubKey(bip44Path []uint32, curve Crypto
206196
}
207197

208198
func (ledger *LedgerFilecoin) GetBip44bytes(bip44Path []uint32, hardenCount int) ([]byte, error) {
209-
pathBytes, err := GetBip44bytes(bip44Path, hardenCount)
199+
return GetBip44bytes(bip44Path, hardenCount)
200+
}
201+
202+
func (ledger *LedgerFilecoin) sign(bip44Path []uint32, transaction []byte, curve CryptoCurve) ([]byte, error) {
203+
if err := isCryptoCurveSupported(curve); err != nil {
204+
return nil, err
205+
}
206+
207+
pathBytes, err := ledger.GetBip44bytes(bip44Path, HardenCount)
208+
if err != nil {
209+
return nil, err
210+
}
211+
212+
chunks, err := prepareChunks(pathBytes, transaction)
210213
if err != nil {
211214
return nil, err
212215
}
213216

214-
return pathBytes, nil
217+
return ledger.processChunks(chunks, INSSign)
215218
}
216219

217-
func (ledger *LedgerFilecoin) sign(bip44Path []uint32, transaction []byte, curve CryptoCurve) ([]byte, error) {
218-
if err := isCryptoCurveSupported(curve); err != nil {
220+
// SignPersonalMessageFVM signs a personal message for FVM (Filecoin Virtual Machine)
221+
// this command requires user confirmation in the device
222+
func (ledger *LedgerFilecoin) SignPersonalMessageFVM(bip44Path []uint32, message []byte) (*SignatureAnswer, error) {
223+
// Personal messages are always signed with SECP256K1
224+
signatureBytes, err := ledger.signPersonalMessage(bip44Path, message)
225+
if err != nil {
219226
return nil, err
220227
}
221228

229+
return parseSignatureResponse(signatureBytes)
230+
}
231+
232+
func (ledger *LedgerFilecoin) signPersonalMessage(bip44Path []uint32, message []byte) ([]byte, error) {
222233
pathBytes, err := ledger.GetBip44bytes(bip44Path, HardenCount)
223234
if err != nil {
224235
return nil, err
225236
}
226237

227-
chunks, err := prepareChunks(pathBytes, transaction)
238+
// Prepend message length as 4 bytes (big endian)
239+
messageLen := uint32(len(message))
240+
fullMessage := make([]byte, messageLengthPrefixSize+len(message))
241+
binary.BigEndian.PutUint32(fullMessage[0:messageLengthPrefixSize], messageLen)
242+
copy(fullMessage[messageLengthPrefixSize:], message)
243+
244+
chunks, err := prepareChunks(pathBytes, fullMessage)
228245
if err != nil {
229246
return nil, err
230247
}
231248

232-
var finalResponse []byte
249+
return ledger.processChunks(chunks, INSSignPersonalMsg)
250+
}
233251

234-
var message []byte
252+
// retrieveAddressPubKey returns the pubkey and address
253+
// SignRawBytes signs raw bytes using Filecoin user app
254+
// this command requires user confirmation in the device
255+
func (ledger *LedgerFilecoin) SignRawBytes(bip44Path []uint32, message []byte) (*SignatureAnswer, error) {
256+
signatureBytes, err := ledger.signRawBytes(bip44Path, message)
257+
if err != nil {
258+
return nil, err
259+
}
235260

236-
var chunkIndex int = 0
261+
return parseSignatureResponse(signatureBytes)
262+
}
237263

238-
for chunkIndex < len(chunks) {
239-
payloadLen := byte(len(chunks[chunkIndex]))
264+
func (ledger *LedgerFilecoin) signRawBytes(bip44Path []uint32, message []byte) ([]byte, error) {
265+
pathBytes, err := ledger.GetBip44bytes(bip44Path, HardenCount)
266+
if err != nil {
267+
return nil, err
268+
}
240269

241-
if chunkIndex == 0 {
242-
header := []byte{CLA, INSSign, PayloadChunkInit, 0, payloadLen}
243-
message = append(header, chunks[chunkIndex]...)
244-
} else {
270+
// Encode message length using varint and prepend to message
271+
messageLen := len(message)
272+
varintLen := encodeVarint(uint64(messageLen))
273+
fullMessage := make([]byte, len(varintLen)+len(message))
274+
copy(fullMessage, varintLen)
275+
copy(fullMessage[len(varintLen):], message)
245276

246-
payloadDesc := byte(PayloadChunkAdd)
247-
if chunkIndex == (len(chunks) - 1) {
248-
payloadDesc = byte(PayloadChunkLast)
249-
}
277+
chunks, err := prepareChunks(pathBytes, fullMessage)
278+
if err != nil {
279+
return nil, err
280+
}
281+
282+
return ledger.processChunks(chunks, INSSignRawBytes)
283+
}
250284

251-
header := []byte{CLA, INSSign, payloadDesc, 0, payloadLen}
252-
message = append(header, chunks[chunkIndex]...)
285+
func parseSignatureResponse(signatureBytes []byte) (*SignatureAnswer, error) {
286+
if len(signatureBytes) < signatureMinLength {
287+
return nil, fmt.Errorf("signature too short: expected at least %d bytes, got %d", signatureMinLength, len(signatureBytes))
288+
}
289+
290+
return &SignatureAnswer{
291+
r: signatureBytes[signatureROffset : signatureROffset+signatureRLength],
292+
s: signatureBytes[signatureSOffset : signatureSOffset+signatureSLength],
293+
v: signatureBytes[signatureVOffset],
294+
derSignature: signatureBytes[signatureDEROffset:],
295+
}, nil
296+
}
297+
298+
func (ledger *LedgerFilecoin) processChunks(chunks [][]byte, instruction byte) ([]byte, error) {
299+
var finalResponse []byte
300+
301+
for chunkIndex, chunk := range chunks {
302+
payloadLen := byte(len(chunk))
303+
payloadDesc := PayloadChunkAdd
304+
305+
if chunkIndex == 0 {
306+
payloadDesc = PayloadChunkInit
307+
} else if chunkIndex == len(chunks)-1 {
308+
payloadDesc = PayloadChunkLast
253309
}
254310

311+
header := []byte{CLA, instruction, byte(payloadDesc), apduP2Default, payloadLen}
312+
message := append(header, chunk...)
313+
255314
response, err := ledger.api.Exchange(message)
256315
if err != nil {
257-
// FIXME: CBOR will be used instead
258-
if err.Error() == "[APDU_CODE_BAD_KEY_HANDLE] The parameters in the data field are incorrect" {
259-
// In this special case, we can extract additional info
260-
errorMsg := string(response)
261-
return nil, fmt.Errorf(errorMsg)
262-
}
263-
if err.Error() == "[APDU_CODE_DATA_INVALID] Referenced data reversibly blocked (invalidated)" {
264-
errorMsg := string(response)
265-
return nil, fmt.Errorf(errorMsg)
266-
}
267-
return nil, err
316+
return nil, handleExchangeError(err, response)
268317
}
269318

270319
finalResponse = response
271-
chunkIndex++
272-
273320
}
321+
274322
return finalResponse, nil
275323
}
276324

277-
// retrieveAddressPubKey returns the pubkey and address
325+
func handleExchangeError(err error, response []byte) error {
326+
errorMsg := err.Error()
327+
switch {
328+
case errorMsg == "[APDU_CODE_BAD_KEY_HANDLE] The parameters in the data field are incorrect":
329+
return fmt.Errorf(string(response))
330+
case errorMsg == "[APDU_CODE_DATA_INVALID] Referenced data reversibly blocked (invalidated)":
331+
return fmt.Errorf(string(response))
332+
default:
333+
return err
334+
}
335+
}
336+
337+
func encodeVarint(value uint64) []byte {
338+
buf := make([]byte, binary.MaxVarintLen64)
339+
n := binary.PutUvarint(buf, value)
340+
return buf[:n]
341+
}
342+
278343
func (ledger *LedgerFilecoin) retrieveAddressPubKey(bip44Path []uint32, curve CryptoCurve, requireConfirmation bool) (pubkey []byte, addrByte []byte, addrString string, err error) {
279344
if err := isCryptoCurveSupported(curve); err != nil {
280345
return nil, nil, "", err
@@ -285,23 +350,23 @@ func (ledger *LedgerFilecoin) retrieveAddressPubKey(bip44Path []uint32, curve Cr
285350
return nil, nil, "", err
286351
}
287352

288-
p1 := byte(0)
353+
p1 := byte(apduP1Default)
289354
if requireConfirmation {
290-
p1 = byte(1)
355+
p1 = byte(apduP1Confirm)
291356
}
292357

293358
// Prepare message
294-
header := []byte{CLA, INSGetAddr, p1, 0, 0}
359+
header := []byte{CLA, INSGetAddr, p1, apduP2Default, 0}
295360
message := append(header, pathBytes...)
296-
message[4] = byte(len(message) - len(header)) // update length
361+
message[apduDataLenOffset] = byte(len(message) - len(header)) // update length
297362

298363
response, err := ledger.api.Exchange(message)
299364

300365
if err != nil {
301366
return nil, nil, "", err
302367
}
303-
if len(response) < 39 {
304-
return nil, nil, "", fmt.Errorf("Invalid response")
368+
if len(response) < minAddressResponseLength {
369+
return nil, nil, "", fmt.Errorf("invalid address response length: expected %d, got %d", minAddressResponseLength, len(response))
305370
}
306371

307372
cursor := 0
@@ -312,15 +377,15 @@ func (ledger *LedgerFilecoin) retrieveAddressPubKey(bip44Path []uint32, curve Cr
312377

313378
// Read addr byte format length
314379
addrByteLength := int(response[cursor])
315-
cursor = cursor + 1
380+
cursor = cursor + lengthByteSize
316381

317382
// Read addr byte format
318383
addrByte = response[cursor : cursor+addrByteLength]
319384
cursor = cursor + addrByteLength
320385

321386
// Read addr strin format length
322387
addrStringLength := int(response[cursor])
323-
cursor = cursor + 1
388+
cursor = cursor + lengthByteSize
324389

325390
// Read addr string format
326391
addrString = string(response[cursor : cursor+addrStringLength])

0 commit comments

Comments
 (0)