Skip to content

Commit

Permalink
Merge pull request #1236 from onflow/crypto-functions
Browse files Browse the repository at this point in the history
Add BLS-specific crypto functions to cadence
  • Loading branch information
dsainati1 authored Nov 16, 2021
2 parents 3c1d3fe + 1d35e3a commit ad6895c
Show file tree
Hide file tree
Showing 10 changed files with 714 additions and 6 deletions.
25 changes: 25 additions & 0 deletions docs/language/crypto.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ struct PublicKey {
domainSeparationTag: String,
hashAlgorithm: HashAlgorithm
): Bool
/// Verifies the proof of possession of the private key.
/// This function is only implemented if the signature algorithm of the public key is BLS, it errors if called with any other signature algorithm.
pub fun verifyPoP(_ proof: [UInt8]): Bool
}
```

Expand Down Expand Up @@ -185,6 +189,27 @@ The inputs to `verify()` depend on the signature scheme used:

BLS verification performs the necessary membership check of the signature while the membership check of the public key is performed at the creation of the `PublicKey` object.

The BLS signature scheme also supports two additional operations on keys and signatures:

```cadence
/// This is a specific function for the BLS signature scheme. It aggregate multiple BLS signatures into one,
/// considering the proof of possession as a defence against the rogue attacks.
///
/// Signatures could be generated from the same or distinct messages, they
/// could also be the aggregation of other signatures.
/// The order of the signatures in the slice does not matter since the aggregation is commutative.
/// No subgroup membership check is performed on the input signatures.
/// The function errors if the array is empty or if decoding one of the signature fails.
AggregateBLSSignatures(_ signatures: [[UInt8]]): [UInt8]
/// This is a specific function for the BLS signature scheme. It aggregates multiple BLS public keys into one.
///
/// The order of the public keys in the slice does not matter since the aggregation is commutative.
/// No subgroup membership check is performed on the input keys.
/// The function errors if the array is empty or any of the input keys is not a BLS key.
AggregateBLSPublicKeys(_ signatures: [PublicKey]): PublicKey
```

## Crypto Contract

The built-in contract `Crypto` can be used to perform cryptographic operations.
Expand Down
180 changes: 180 additions & 0 deletions runtime/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,183 @@ func TestRuntimeHashAlgorithmImport(t *testing.T) {
testHashAlgorithm(algo)
}
}

func TestBLSVerifyPoP(t *testing.T) {

t.Parallel()

runtime := newTestInterpreterRuntime()

script := []byte(`
pub fun main(): Bool {
let publicKey = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.BLS_BLS12_381
)
return publicKey.verifyPoP([1, 2, 3, 4, 5])
}
`)

called := false

storage := newTestLedger(nil, nil)

runtimeInterface := &testRuntimeInterface{
storage: storage,
bLSVerifyPOP: func(
pk *PublicKey,
proof []byte,
) (bool, error) {
assert.Equal(t, pk.PublicKey, []byte{1, 2})
called = true
return true, nil
},
}

result, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: utils.TestLocation,
},
)
require.NoError(t, err)

assert.Equal(t,
cadence.NewBool(true),
result,
)

assert.True(t, called)
}

func TestBLSAggregateSignatures(t *testing.T) {

t.Parallel()

runtime := newTestInterpreterRuntime()

script := []byte(`
pub fun main(): [UInt8] {
return AggregateBLSSignatures([
[1, 1, 1, 1, 1],
[2, 2, 2, 2, 2],
[3, 3, 3, 3, 3],
[4, 4, 4, 4, 4],
[5, 5, 5, 5, 5]
])
}
`)

called := false

storage := newTestLedger(nil, nil)

runtimeInterface := &testRuntimeInterface{
storage: storage,
aggregateBLSSignatures: func(
sigs [][]byte,
) ([]byte, error) {
assert.Equal(t, len(sigs), 5)
ret := make([]byte, 0, len(sigs[0]))
for i, sig := range sigs {
ret = append(ret, sig[i])
}
called = true
return ret, nil
},
}

result, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: utils.TestLocation,
},
)
require.NoError(t, err)

assert.Equal(t,
cadence.NewArray([]cadence.Value{
cadence.UInt8(1),
cadence.UInt8(2),
cadence.UInt8(3),
cadence.UInt8(4),
cadence.UInt8(5),
}),
result,
)

assert.True(t, called)
}

func TestAggregateBLSPublicKeys(t *testing.T) {

t.Parallel()

runtime := newTestInterpreterRuntime()

script := []byte(`
pub fun main(): PublicKey {
let k1 = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.BLS_BLS12_381
)
let k2 = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.BLS_BLS12_381
)
return AggregateBLSPublicKeys([k1, k2])
}
`)

called := false

storage := newTestLedger(nil, nil)

runtimeInterface := &testRuntimeInterface{
storage: storage,
aggregateBLSPublicKeys: func(
keys []*PublicKey,
) (*PublicKey, error) {
assert.Equal(t, len(keys), 2)
ret := make([]byte, 0, len(keys))
for _, key := range keys {
ret = append(ret, key.PublicKey...)
}
called = true
return &PublicKey{PublicKey: ret, SignAlgo: SignatureAlgorithmBLS_BLS12_381}, nil
},
}

result, err := runtime.ExecuteScript(
Script{
Source: script,
},
Context{
Interface: runtimeInterface,
Location: utils.TestLocation,
},
)
require.NoError(t, err)

assert.Equal(t,
cadence.NewArray([]cadence.Value{
cadence.UInt8(1),
cadence.UInt8(2),
cadence.UInt8(1),
cadence.UInt8(2),
}),
result.(cadence.Struct).Fields[0],
)

assert.True(t, called)
}
6 changes: 6 additions & 0 deletions runtime/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ type Interface interface {
GetAccountContractNames(address Address) ([]string, error)
// RecordTrace records a opentracing trace
RecordTrace(operation string, location common.Location, duration time.Duration, logs []opentracing.LogRecord)
// BLSVerifyPOP verifies a proof of possession (PoP) for the receiver public key.
BLSVerifyPOP(pk *PublicKey, s []byte) (bool, error)
// AggregateBLSSignatures aggregate multiple BLS signatures into one.
AggregateBLSSignatures(sigs [][]byte) ([]byte, error)
// AggregateBLSPublicKeys aggregate multiple BLS public keys into one.
AggregateBLSPublicKeys(keys []*PublicKey) (*PublicKey, error)
}

type Metrics interface {
Expand Down
56 changes: 56 additions & 0 deletions runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,28 @@ type PublicKeyValidationHandlerFunc func(
publicKey *CompositeValue,
) BoolValue

// VerifyBLSPoPHandlerFunc is a function that verifies a BLS proof of possession
type VerifyBLSPoPHandlerFunc func(
interpreter *Interpreter,
getLocationRange func() LocationRange,
publicKey MemberAccessibleValue,
signature []byte,
) (BoolValue, error)

// AggregateBLSSignaturesHandlerFunc is a function that joins a list of
// BLS signatures
type AggregateBLSSignaturesHandlerFunc func(
signatures [][]byte,
) ([]byte, error)

// AggregateBLSPublicKeysHandlerFunc is a function that joins a list of
// BLS public keys
type AggregateBLSPublicKeysHandlerFunc func(
interpreter *Interpreter,
getLocationRange func() LocationRange,
publicKeys []MemberAccessibleValue,
) (MemberAccessibleValue, error)

// SignatureVerificationHandlerFunc is a function that validates a signature.
type SignatureVerificationHandlerFunc func(
interpreter *Interpreter,
Expand Down Expand Up @@ -275,6 +297,9 @@ type Interpreter struct {
uuidHandler UUIDHandlerFunc
PublicKeyValidationHandler PublicKeyValidationHandlerFunc
SignatureVerificationHandler SignatureVerificationHandlerFunc
BLSVerifyPoPHandler VerifyBLSPoPHandlerFunc
AggregateBLSSignaturesHandler AggregateBLSSignaturesHandlerFunc
AggregateBLSPublicKeysHandler AggregateBLSPublicKeysHandlerFunc
HashHandler HashHandlerFunc
ExitHandler ExitHandlerFunc
interpreted bool
Expand Down Expand Up @@ -437,6 +462,24 @@ func WithPublicKeyValidationHandler(handler PublicKeyValidationHandlerFunc) Opti
}
}

// WithBLSCryptoFunctions returns an interpreter option which sets the given
// functions as the functions used to handle certain BLS-specific crypto functions.
//
func WithBLSCryptoFunctions(
verifyPoP VerifyBLSPoPHandlerFunc,
aggregateSignatures AggregateBLSSignaturesHandlerFunc,
aggregatePublicKeys AggregateBLSPublicKeysHandlerFunc,
) Option {
return func(interpreter *Interpreter) error {
interpreter.SetBLSCryptoFunctions(
verifyPoP,
aggregateSignatures,
aggregatePublicKeys,
)
return nil
}
}

// WithSignatureVerificationHandler returns an interpreter option which sets the given
// function as the function that is used to handle signature validation.
//
Expand Down Expand Up @@ -642,6 +685,18 @@ func (interpreter *Interpreter) SetPublicKeyValidationHandler(function PublicKey
interpreter.PublicKeyValidationHandler = function
}

// SetBLSCryptoFunctions sets the functions that are used to handle certain BLS specific crypt functions.
//
func (interpreter *Interpreter) SetBLSCryptoFunctions(
verifyPoP VerifyBLSPoPHandlerFunc,
aggregateSignatures AggregateBLSSignaturesHandlerFunc,
aggregatePublicKeys AggregateBLSPublicKeysHandlerFunc,
) {
interpreter.BLSVerifyPoPHandler = verifyPoP
interpreter.AggregateBLSSignaturesHandler = aggregateSignatures
interpreter.AggregateBLSPublicKeysHandler = aggregatePublicKeys
}

// SetSignatureVerificationHandler sets the function that is used to handle signature validation.
//
func (interpreter *Interpreter) SetSignatureVerificationHandler(function SignatureVerificationHandlerFunc) {
Expand Down Expand Up @@ -2416,6 +2471,7 @@ func (interpreter *Interpreter) NewSubInterpreter(
WithPublicKeyValidationHandler(interpreter.PublicKeyValidationHandler),
WithSignatureVerificationHandler(interpreter.SignatureVerificationHandler),
WithHashHandler(interpreter.HashHandler),
WithBLSCryptoFunctions(interpreter.BLSVerifyPoPHandler, interpreter.AggregateBLSSignaturesHandler, interpreter.AggregateBLSPublicKeysHandler),
}

return NewInterpreter(
Expand Down
40 changes: 39 additions & 1 deletion runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -11251,7 +11251,8 @@ func NewPublicKeyValue(
},
}
publicKeyValue.Functions = map[string]FunctionValue{
sema.PublicKeyVerifyFunction: publicKeyVerifyFunction,
sema.PublicKeyVerifyFunction: publicKeyVerifyFunction,
sema.PublicKeyVerifyPoPFunction: publicKeyVerifyPoPFunction,
}

// Validate the public key, and initialize 'isValid' field.
Expand Down Expand Up @@ -11322,3 +11323,40 @@ var publicKeyVerifyFunction = NewHostFunctionValue(
},
sema.PublicKeyVerifyFunctionType,
)

var publicKeyVerifyPoPFunction = NewHostFunctionValue(
func(invocation Invocation) (v Value) {
signatureValue := invocation.Arguments[0].(*ArrayValue)
publicKey := invocation.Self

interpreter := invocation.Interpreter

getLocationRange := invocation.GetLocationRange

interpreter.ExpectType(
publicKey,
sema.PublicKeyType,
getLocationRange,
)

bytesArray := make([]byte, 0, signatureValue.Count())
signatureValue.Iterate(func(element Value) (resume bool) {
b := element.(UInt8Value)
bytesArray = append(bytesArray, byte(b))
return true
})

var err error
v, err = interpreter.BLSVerifyPoPHandler(
interpreter,
getLocationRange,
publicKey,
bytesArray,
)
if err != nil {
panic(err)
}
return
},
sema.PublicKeyVerifyPoPFunctionType,
)
Loading

0 comments on commit ad6895c

Please sign in to comment.