Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new crypto features #852

Merged
merged 19 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/language/accounts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ struct AccountKey {
struct PublicKey {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can PublicKey fields (data, signature algo, isValid..) be updated after the creation of the object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they are defined as constant fields (i.e. read-only).

let publicKey: [UInt8]
let signatureAlgorithm: SignatureAlgorithm
let isValid: Bool

/// Verifies a signature. Checks whether the signature was produced by signing
/// the given tag and data, using this public key and the given hash algorithm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not quite what the verification checks, I suggest updating the text to be more precise:

    ///  Verifies a signature under the given tag, data and public key. It uses the given hash algorithm to hash the tag and data. 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another point, I'm not sure if this is right place to mention that all verifications with an invalid public key will fail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be fine to add it here, or we can also add it as a note outside of the code block.

pub fun verify(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @turbolent mentioned about breaking changes, I think the change from isValid to verify is a breaking one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, both were added, so this isn't a breaking change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah my bad, I somehow though isValid already existed.

signature: [UInt8],
signedData: [UInt8],
domainSeparationTag: String,
hashAlgorithm: HashAlgorithm
): Bool
}
```

Expand Down
18 changes: 12 additions & 6 deletions docs/language/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub enum HashAlgorithm: UInt8 {

/// SHA3_384 is Secure Hashing Algorithm 3 (SHA-3) with a 384-bit digest.
pub case SHA3_384 = 4

/// KMAC128_BLS_BLS12_381 is an instance of KMAC128 mac algorithm, that can be used
/// as the hashing algorithm for BLS signature scheme on the curve BLS12-381.
pub case KMAC128_BLS_BLS12_381 = 5
}
```

Expand All @@ -31,8 +35,11 @@ pub enum SignatureAlgorithm: UInt8 {
/// ECDSA_P256 is Elliptic Curve Digital Signature Algorithm (ECDSA) on the NIST P-256 curve.
pub case ECDSA_P256 = 1

/// ECDSA_Secp256k1 is Elliptic Curve Digital Signature Algorithm (ECDSA) on the secp256k1 curve.
pub case ECDSA_Secp256k1 = 2
/// ECDSA_secp256k1 is Elliptic Curve Digital Signature Algorithm (ECDSA) on the secp256k1 curve.
pub case ECDSA_secp256k1 = 2

/// BLS_BLS12_381 is BLS signature scheme on the BLS12-381 curve.
pub case BLS_BLS12_381 = 3
}
```

Expand Down Expand Up @@ -99,13 +106,12 @@ The API of the Crypto contract is:
```cadence
pub contract Crypto {

pub struct SignatureAlgorithm {
pub let name: String
}

// Hash the data using the given hashing algorithm and returns the hashed data.
pub fun hash(_ data: [UInt8], algorithm: HashAlgorithm): [UInt8]

// Hash the data using the given hashing algorithm and the tag. Returns the hashed data.
pub fun hashWithTag(_ data: [UInt8], tag: string, algorithm: HashAlgorithm): [UInt8]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have suggested hashWithTag as I wasn't sure if PublicKey.Verify would be implemented entirely by the fvm or would be partly implemented in Cadence code. In the case of the former, I don't think hashWithTag is needed in Cadence, we would only need it as an fvm function.


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I leave this comment here, it's about the function at this line:

Shouldn't we also update isValid to Verify here too? or is that a big breaking change?

pub struct KeyListEntry {
pub let keyIndex: Int
pub let publicKey: PublicKey
Expand Down
245 changes: 240 additions & 5 deletions runtime/account_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/sema"
"github.com/onflow/cadence/runtime/stdlib"
"github.com/onflow/cadence/runtime/tests/utils"
)

func TestRuntimeTransaction_AddPublicKey(t *testing.T) {
Expand Down Expand Up @@ -270,6 +271,8 @@ var accountKeyA = &AccountKey{
PublicKey: &PublicKey{
PublicKey: []byte{1, 2, 3},
SignAlgo: sema.SignatureAlgorithmECDSA_P256,
IsValid: false,
Validated: true,
},
HashAlgo: sema.HashAlgorithmSHA3_256,
Weight: 100,
Expand All @@ -280,7 +283,9 @@ var accountKeyB = &AccountKey{
KeyIndex: 1,
PublicKey: &PublicKey{
PublicKey: []byte{4, 5, 6},
SignAlgo: sema.SignatureAlgorithmECDSA_Secp256k1,
SignAlgo: sema.SignatureAlgorithmECDSA_secp256k1,
IsValid: false,
Validated: false,
},
HashAlgo: sema.HashAlgorithmSHA3_256,
Weight: 100,
Expand Down Expand Up @@ -521,7 +526,7 @@ func TestRuntimePublicAccountKeys(t *testing.T) {

expectedValue := accountKeyExportedValue(1,
[]byte{4, 5, 6},
sema.SignatureAlgorithmECDSA_Secp256k1,
sema.SignatureAlgorithmECDSA_secp256k1,
sema.HashAlgorithmSHA3_256,
"100.0",
false,
Expand Down Expand Up @@ -673,7 +678,7 @@ func TestRuntimeSignatureAlgorithm(t *testing.T) {

script := []byte(`
pub fun main(): [SignatureAlgorithm?] {
var key1: SignatureAlgorithm? = SignatureAlgorithm.ECDSA_Secp256k1
var key1: SignatureAlgorithm? = SignatureAlgorithm.ECDSA_secp256k1

var key2: SignatureAlgorithm? = SignatureAlgorithm(rawValue: 2)

Expand Down Expand Up @@ -711,7 +716,7 @@ func TestRuntimeSignatureAlgorithm(t *testing.T) {

require.Len(t, builtinStruct.Fields, 1)
assert.Equal(t,
cadence.NewUInt8(SignatureAlgorithmECDSA_Secp256k1.RawValue()),
cadence.NewUInt8(SignatureAlgorithmECDSA_secp256k1.RawValue()),
builtinStruct.Fields[0],
)

Expand All @@ -724,7 +729,7 @@ func TestRuntimeSignatureAlgorithm(t *testing.T) {

require.Len(t, builtinStruct.Fields, 1)
assert.Equal(t,
cadence.NewUInt8(SignatureAlgorithmECDSA_Secp256k1.RawValue()),
cadence.NewUInt8(SignatureAlgorithmECDSA_secp256k1.RawValue()),
builtinStruct.Fields[0],
)

Expand Down Expand Up @@ -795,6 +800,9 @@ func accountKeyExportedValue(

// Signature Algo
newSignAlgoValue(signAlgo),

// valid
cadence.Bool(false),
},
},

Expand Down Expand Up @@ -966,3 +974,230 @@ type testAccountKeyStorage struct {
keys []*AccountKey
returnedKey *AccountKey
}

func TestPublicKey(t *testing.T) {

t.Parallel()

rt := NewInterpreterRuntime()
runtimeInterface := &testRuntimeInterface{}

executeScript := func(code string, runtimeInterface Interface) (cadence.Value, error) {
return rt.ExecuteScript(
Script{
Source: []byte(code),
},
Context{
Interface: runtimeInterface,
Location: utils.TestLocation,
},
)
}

t.Run("Constructor", func(t *testing.T) {
script := `
pub fun main(): PublicKey {
let publicKey = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)

return publicKey
}
`

value, err := executeScript(script, runtimeInterface)
require.NoError(t, err)

expected := cadence.Struct{
StructType: PublicKeyType,
Fields: []cadence.Value{
// Public key (bytes)
newBytesValue([]byte{1, 2}),

// Signature Algo
newSignAlgoValue(sema.SignatureAlgorithmECDSA_P256),

// valid
cadence.Bool(false),
},
}

assert.Equal(t, expected, value)
})

t.Run("Validate func", func(t *testing.T) {
script := `
pub fun main(): Bool {
let publicKey = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)

return publicKey.validate()
}
`

runtimeInterface := &testRuntimeInterface{
validatePublicKey: func(publicKey *PublicKey) (bool, error) {
return true, nil
},
}

_, err := executeScript(script, runtimeInterface)
require.Error(t, err)
assert.Contains(t, err.Error(), "value of type `PublicKey` has no member `validate`")
})

t.Run("IsValid", func(t *testing.T) {
for _, validity := range []bool{true, false} {
script := `
pub fun main(): Bool {
let publicKey = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)

return publicKey.isValid
}
`
invoked := false
validateMethodReturnValue := validity

runtimeInterface := &testRuntimeInterface{
validatePublicKey: func(publicKey *PublicKey) (bool, error) {
invoked = true
return validateMethodReturnValue, nil
},
}

value, err := executeScript(script, runtimeInterface)
require.NoError(t, err)

assert.True(t, invoked)
assert.Equal(t, cadence.Bool(validateMethodReturnValue), value)

}
})

t.Run("IsValid - publicKey from host env", func(t *testing.T) {

storage := newTestAccountKeyStorage()
storage.keys = append(storage.keys, accountKeyA, accountKeyB)

for index, key := range storage.keys {
script := fmt.Sprintf(`
pub fun main(): Bool {
// Get a public key from host env
let acc = getAccount(0x02)
let publicKey = acc.keys.get(keyIndex: %d)!.publicKey
return publicKey.isValid
}
`, index)

invoked := false
validateMethodReturnValue := true

runtimeInterface := getAccountKeyTestRuntimeInterface(storage)
runtimeInterface.validatePublicKey = func(publicKey *PublicKey) (bool, error) {
invoked = true
return validateMethodReturnValue, nil
}

value, err := executeScript(script, runtimeInterface)
require.NoError(t, err)

// If already validated, then the validation func shouldn't get re-invoked
assert.NotEqual(t, key.PublicKey.Validated, invoked)

// If validated, `isValid` should have the same value as `publicKey.IsValid`.
// Otherwise, it should give the value returned by the `validate()` func.
isValid := validateMethodReturnValue
if key.PublicKey.Validated {
isValid = key.PublicKey.IsValid
}

assert.Equal(t, cadence.Bool(isValid), value)
}
})

t.Run("Verify", func(t *testing.T) {
script := `
pub fun main(): Bool {
let publicKey = PublicKey(
publicKey: "0102".decodeHex(),
signatureAlgorithm: SignatureAlgorithm.ECDSA_P256
)

return publicKey.verify(
signature: [],
signedData: [],
domainSeparationTag: "something",
hashAlgorithm: HashAlgorithm.SHA2_256
)
}
`
invoked := false

runtimeInterface := &testRuntimeInterface{
verifySignature: func(
_ []byte,
_ string,
_ []byte,
_ []byte,
_ SignatureAlgorithm,
_ HashAlgorithm,
) (bool, error) {
invoked = true
return true, nil
},
}

value, err := executeScript(script, runtimeInterface)
require.NoError(t, err)

assert.True(t, invoked)
assert.Equal(t, cadence.Bool(true), value)
})

t.Run("Verify - publicKey from host env", func(t *testing.T) {

storage := newTestAccountKeyStorage()
storage.keys = append(storage.keys, accountKeyA, accountKeyB)

script := `
pub fun main(): Bool {
// Get a public key from host env
let acc = getAccount(0x02)
let publicKey = acc.keys.get(keyIndex: 0)!.publicKey

return publicKey.verify(
signature: [],
signedData: [],
domainSeparationTag: "something",
hashAlgorithm: HashAlgorithm.SHA2_256
)
}
`
invoked := false

runtimeInterface := getAccountKeyTestRuntimeInterface(storage)
runtimeInterface.verifySignature = func(
_ []byte,
_ string,
_ []byte,
_ []byte,
_ SignatureAlgorithm,
_ HashAlgorithm,
) (bool, error) {
invoked = true
return true, nil
}

value, err := executeScript(script, runtimeInterface)
require.NoError(t, err)

assert.True(t, invoked)
assert.Equal(t, cadence.Bool(true), value)
})
}
Loading