Skip to content

Commit

Permalink
fix: improve ffi safety
Browse files Browse the repository at this point in the history
- *mut ptr to indicate ownership
- Boxed slices instead of vecs for safe transfers
- expanded checks for null ptrs

Closes filecoin-project/rust-fil-ffi-toolkit#9
  • Loading branch information
dignifiedquire committed Apr 12, 2022
1 parent 11bdfe3 commit 89f57b6
Show file tree
Hide file tree
Showing 45 changed files with 4,310 additions and 13,103 deletions.
14 changes: 3 additions & 11 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ orbs:
executors:
golang:
docker:
- image: circleci/golang:1.13
- image: circleci/golang:1.17
resource_class: 2xlarge
rust:
docker:
Expand All @@ -32,7 +32,7 @@ jobs:
- go/mod-download
- go/install-golangci-lint:
gobin: $HOME/.local/bin
version: 1.32.0
version: 1.45.2
- run:
command: make go-lint

Expand Down Expand Up @@ -73,7 +73,6 @@ jobs:
- run: cd rust && cargo install cargo-lipo
- build_project
- compile_tests
- ensure_generated_cgo_up_to_date
publish_linux_staticlib:
executor: golang
steps:
Expand Down Expand Up @@ -209,7 +208,7 @@ commands:
- run:
name: Install Go
command: |
curl https://dl.google.com/go/go1.13.7.darwin-amd64.pkg -o /tmp/go.pkg && \
curl https://dl.google.com/go/go1.17.8.darwin-amd64.pkg -o /tmp/go.pkg && \
sudo installer -pkg /tmp/go.pkg -target /
go version
- run:
Expand Down Expand Up @@ -312,13 +311,6 @@ commands:
name: Build project without CGO
command: env CGO_ENABLED=0 go build .

ensure_generated_cgo_up_to_date:
steps:
- run:
name: Generate CGO bindings (using forked c-for-go) and compare with what's tracked by Git
command: |
make cgo-gen
git diff --exit-code ./generated/
run_tests:
parameters:
run_leak_detector:
Expand Down
119 changes: 32 additions & 87 deletions bls.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+build cgo
//go:build cgo
// +build cgo

package ffi

Expand All @@ -7,24 +8,16 @@ package ffi
// #include "./filcrypto.h"
import "C"
import (
"github.com/filecoin-project/filecoin-ffi/generated"
"github.com/filecoin-project/filecoin-ffi/cgo"
)

// Hash computes the digest of a message
func Hash(message Message) Digest {
resp := generated.FilHash(message, uint(len(message)))
resp.Deref()
resp.Digest.Deref()

defer generated.FilDestroyHashResponse(resp)

var out Digest
copy(out[:], resp.Digest.Inner[:])
return out
func Hash(message Message) *Digest {
return cgo.Hash(cgo.AsSliceRefUint8(message))
}

// Verify verifies that a signature is the aggregated signature of digests - pubkeys
func Verify(signature *Signature, digests []Digest, publicKeys []PublicKey) bool {
func Verify(signature *Signature, digests []Digest, publicKeys []*PublicKey) bool {
// prep data
flattenedDigests := make([]byte, DigestBytes*len(digests))
for idx, digest := range digests {
Expand All @@ -36,13 +29,15 @@ func Verify(signature *Signature, digests []Digest, publicKeys []PublicKey) bool
copy(flattenedPublicKeys[(PublicKeyBytes*idx):(PublicKeyBytes*(1+idx))], publicKey[:])
}

isValid := generated.FilVerify(signature[:], flattenedDigests, uint(len(flattenedDigests)), flattenedPublicKeys, uint(len(flattenedPublicKeys)))

return isValid > 0
return cgo.Verify(
cgo.AsSliceRefUint8(signature[:]),
cgo.AsSliceRefUint8(flattenedDigests),
cgo.AsSliceRefUint8(flattenedPublicKeys),
)
}

// HashVerify verifies that a signature is the aggregated signature of hashed messages.
func HashVerify(signature *Signature, messages []Message, publicKeys []PublicKey) bool {
func HashVerify(signature *Signature, messages []Message, publicKeys []*PublicKey) bool {
var flattenedMessages []byte
messagesSizes := make([]uint, len(messages))
for idx := range messages {
Expand All @@ -55,99 +50,49 @@ func HashVerify(signature *Signature, messages []Message, publicKeys []PublicKey
copy(flattenedPublicKeys[(PublicKeyBytes*idx):(PublicKeyBytes*(1+idx))], publicKey[:])
}

isValid := generated.FilHashVerify(signature[:], flattenedMessages, uint(len(flattenedMessages)), messagesSizes, uint(len(messagesSizes)), flattenedPublicKeys, uint(len(flattenedPublicKeys)))

return isValid > 0
return cgo.HashVerify(
cgo.AsSliceRefUint8(signature[:]),
cgo.AsSliceRefUint8(flattenedMessages),
cgo.AsSliceRefUint(messagesSizes),
cgo.AsSliceRefUint8(flattenedPublicKeys),
)
}

// Aggregate aggregates signatures together into a new signature. If the
// provided signatures cannot be aggregated (due to invalid input or an
// an operational error), Aggregate will return nil.
func Aggregate(signatures []Signature) *Signature {
func Aggregate(signatures []*Signature) *Signature {
// prep data
flattenedSignatures := make([]byte, SignatureBytes*len(signatures))
for idx, sig := range signatures {
copy(flattenedSignatures[(SignatureBytes*idx):(SignatureBytes*(1+idx))], sig[:])
}

resp := generated.FilAggregate(flattenedSignatures, uint(len(flattenedSignatures)))
if resp == nil {
return nil
}

defer generated.FilDestroyAggregateResponse(resp)

resp.Deref()
resp.Signature.Deref()

var out Signature
copy(out[:], resp.Signature.Inner[:])
return &out
return cgo.Aggregate(cgo.AsSliceRefUint8(flattenedSignatures))
}

// PrivateKeyGenerate generates a private key
func PrivateKeyGenerate() PrivateKey {
resp := generated.FilPrivateKeyGenerate()
resp.Deref()
resp.PrivateKey.Deref()
defer generated.FilDestroyPrivateKeyGenerateResponse(resp)

var out PrivateKey
copy(out[:], resp.PrivateKey.Inner[:])
return out
func PrivateKeyGenerate() *PrivateKey {
return cgo.PrivateKeyGenerate()
}

// PrivateKeyGenerate generates a private key in a predictable manner
func PrivateKeyGenerateWithSeed(seed PrivateKeyGenSeed) PrivateKey {
var ary generated.Fil32ByteArray
copy(ary.Inner[:], seed[:])

resp := generated.FilPrivateKeyGenerateWithSeed(ary)
resp.Deref()
resp.PrivateKey.Deref()
defer generated.FilDestroyPrivateKeyGenerateResponse(resp)

var out PrivateKey
copy(out[:], resp.PrivateKey.Inner[:])
return out
// PrivateKeyGenerate generates a private key in a predictable manner.
func PrivateKeyGenerateWithSeed(seed PrivateKeyGenSeed) *PrivateKey {
ary := cgo.AsByteArray32(seed[:])
return cgo.PrivateKeyGenerateWithSeed(&ary)
}

// PrivateKeySign signs a message
func PrivateKeySign(privateKey PrivateKey, message Message) *Signature {
resp := generated.FilPrivateKeySign(privateKey[:], message, uint(len(message)))
resp.Deref()
resp.Signature.Deref()

defer generated.FilDestroyPrivateKeySignResponse(resp)

var signature Signature
copy(signature[:], resp.Signature.Inner[:])
return &signature
func PrivateKeySign(privateKey *PrivateKey, message Message) *Signature {
return cgo.PrivateKeySign(cgo.AsSliceRefUint8(privateKey[:]), cgo.AsSliceRefUint8(message))
}

// PrivateKeyPublicKey gets the public key for a private key
func PrivateKeyPublicKey(privateKey PrivateKey) PublicKey {
resp := generated.FilPrivateKeyPublicKey(privateKey[:])
resp.Deref()
resp.PublicKey.Deref()

defer generated.FilDestroyPrivateKeyPublicKeyResponse(resp)

var publicKey PublicKey
copy(publicKey[:], resp.PublicKey.Inner[:])
return publicKey
func PrivateKeyPublicKey(privateKey *PrivateKey) *PublicKey {
return cgo.PrivateKeyPublicKey(cgo.AsSliceRefUint8(privateKey[:]))
}

// CreateZeroSignature creates a zero signature, used as placeholder in filecoin.
func CreateZeroSignature() Signature {
resp := generated.FilCreateZeroSignature()
resp.Deref()
resp.Signature.Deref()

defer generated.FilDestroyZeroSignatureResponse(resp)

var sig Signature
copy(sig[:], resp.Signature.Inner[:])

return sig
func CreateZeroSignature() *Signature {
return cgo.CreateZeroSignature()
}
38 changes: 19 additions & 19 deletions bls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,33 @@ func TestBLSSigningAndVerification(t *testing.T) {
barSignature := PrivateKeySign(barPrivateKey, barMessage)

// get the aggregateSign
aggregateSign := Aggregate([]Signature{*fooSignature, *barSignature})
aggregateSign := Aggregate([]*Signature{fooSignature, barSignature})

// assert the foo message was signed with the foo key
assert.True(t, Verify(fooSignature, []Digest{fooDigest}, []PublicKey{fooPublicKey}))
assert.True(t, Verify(fooSignature, []Digest{*fooDigest}, []*PublicKey{fooPublicKey}))

// assert the bar message was signed with the bar key
assert.True(t, Verify(barSignature, []Digest{barDigest}, []PublicKey{barPublicKey}))
assert.True(t, Verify(barSignature, []Digest{*barDigest}, []*PublicKey{barPublicKey}))

// assert the foo message was signed with the foo key
assert.True(t, HashVerify(fooSignature, []Message{fooMessage}, []PublicKey{fooPublicKey}))
assert.True(t, HashVerify(fooSignature, []Message{fooMessage}, []*PublicKey{fooPublicKey}))

// assert the bar message was signed with the bar key
assert.True(t, HashVerify(barSignature, []Message{barMessage}, []PublicKey{barPublicKey}))
assert.True(t, HashVerify(barSignature, []Message{barMessage}, []*PublicKey{barPublicKey}))

// assert the foo message was not signed by the bar key
assert.False(t, Verify(fooSignature, []Digest{fooDigest}, []PublicKey{barPublicKey}))
assert.False(t, Verify(fooSignature, []Digest{*fooDigest}, []*PublicKey{barPublicKey}))

// assert the bar/foo message was not signed by the foo/bar key
assert.False(t, Verify(barSignature, []Digest{barDigest}, []PublicKey{fooPublicKey}))
assert.False(t, Verify(barSignature, []Digest{fooDigest}, []PublicKey{barPublicKey}))
assert.False(t, Verify(fooSignature, []Digest{barDigest}, []PublicKey{fooPublicKey}))
assert.False(t, Verify(barSignature, []Digest{*barDigest}, []*PublicKey{fooPublicKey}))
assert.False(t, Verify(barSignature, []Digest{*fooDigest}, []*PublicKey{barPublicKey}))
assert.False(t, Verify(fooSignature, []Digest{*barDigest}, []*PublicKey{fooPublicKey}))

//assert the foo and bar message was signed with the foo and bar key
assert.True(t, HashVerify(aggregateSign, []Message{fooMessage, barMessage}, []PublicKey{fooPublicKey, barPublicKey}))
assert.True(t, HashVerify(aggregateSign, []Message{fooMessage, barMessage}, []*PublicKey{fooPublicKey, barPublicKey}))

//assert the bar and foo message was not signed by the foo and bar key
assert.False(t, HashVerify(aggregateSign, []Message{fooMessage, barMessage}, []PublicKey{fooPublicKey}))
assert.False(t, HashVerify(aggregateSign, []Message{fooMessage, barMessage}, []*PublicKey{fooPublicKey}))
}

func BenchmarkBLSVerify(b *testing.B) {
Expand All @@ -90,15 +90,15 @@ func BenchmarkBLSVerify(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
if !Verify(sig, []Digest{digest}, []PublicKey{pubk}) {
if !Verify(sig, []Digest{*digest}, []*PublicKey{pubk}) {
b.Fatal("failed to verify")
}
}
}

func TestBlsAggregateErrors(t *testing.T) {
t.Run("no signatures", func(t *testing.T) {
var empty []Signature
var empty []*Signature
out := Aggregate(empty)
require.Nil(t, out)
})
Expand All @@ -122,15 +122,15 @@ func benchmarkBLSVerifyBatchSize(size int) func(b *testing.B) {
return func(b *testing.B) {
var digests []Digest
var msgs []Message
var sigs []Signature
var pubks []PublicKey
var sigs []*Signature
var pubks []*PublicKey
for i := 0; i < size; i++ {
msg := Message(fmt.Sprintf("cats cats cats cats %d %d %d dogs", i, i, i))
msgs = append(msgs, msg)
digests = append(digests, Hash(msg))
digests = append(digests, *Hash(msg))
priv := PrivateKeyGenerate()
sig := PrivateKeySign(priv, msg)
sigs = append(sigs, *sig)
sigs = append(sigs, sig)
pubk := PrivateKeyPublicKey(priv)
pubks = append(pubks, pubk)
}
Expand Down Expand Up @@ -161,7 +161,7 @@ func BenchmarkBLSHashAndVerify(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
digest := Hash(msg)
if !Verify(sig, []Digest{digest}, []PublicKey{pubk}) {
if !Verify(sig, []Digest{*digest}, []*PublicKey{pubk}) {
b.Fatal("failed to verify")
}
}
Expand All @@ -179,7 +179,7 @@ func BenchmarkBLSHashVerify(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
if !HashVerify(sig, []Message{msg}, []PublicKey{pubk}) {
if !HashVerify(sig, []Message{msg}, []*PublicKey{pubk}) {
b.Fatal("failed to verify")
}
}
Expand Down
60 changes: 60 additions & 0 deletions cgo/bls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cgo

/*
#cgo LDFLAGS: -L${SRCDIR}/..
#cgo pkg-config: ${SRCDIR}/../filcrypto.pc
#include "../filcrypto.h"
#include <stdlib.h>
*/
import "C"

func Hash(message SliceRefUint8) *[96]byte {
resp := C.hash(message)
defer resp.destroy()
return resp.copyAsArray()
}

func Aggregate(flattenedSignatures SliceRefUint8) *[96]byte {
resp := C.aggregate(flattenedSignatures)
defer resp.destroy()
return resp.copyAsArray()
}

func Verify(signature SliceRefUint8, flattenedDigests SliceRefUint8, flattenedPublicKeys SliceRefUint8) bool {
resp := C.verify(signature, flattenedDigests, flattenedPublicKeys)
return bool(resp)
}

func HashVerify(signature SliceRefUint8, flattenedMessages SliceRefUint8, messageSizes SliceRefUint, flattenedPublicKeys SliceRefUint8) bool {
resp := C.hash_verify(signature, flattenedMessages, messageSizes, flattenedPublicKeys)
return bool(resp)
}

func PrivateKeyGenerate() *[32]byte {
resp := C.private_key_generate()
defer resp.destroy()
return resp.copyAsArray()
}

func PrivateKeyGenerateWithSeed(rawSeed *ByteArray32) *[32]byte {
resp := C.private_key_generate_with_seed(rawSeed)
defer resp.destroy()
return resp.copyAsArray()
}

func PrivateKeySign(rawPrivateKey SliceRefUint8, message SliceRefUint8) *[96]byte {
resp := C.private_key_sign(rawPrivateKey, message)
defer resp.destroy()
return resp.copyAsArray()
}

func PrivateKeyPublicKey(rawPrivateKey SliceRefUint8) *[48]byte {
resp := C.private_key_public_key(rawPrivateKey)
defer resp.destroy()
return resp.copyAsArray()
}
func CreateZeroSignature() *[96]byte {
resp := C.create_zero_signature()
defer resp.destroy()
return resp.copyAsArray()
}
Loading

0 comments on commit 89f57b6

Please # to comment.