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 a hash function to the crypto contract #379

Merged
merged 1 commit into from
Sep 18, 2020
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
48 changes: 48 additions & 0 deletions runtime/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,51 @@ func TestRuntimeCrypto_verify(t *testing.T) {

assert.True(t, called)
}

func TestRuntimeCrypto_hash(t *testing.T) {

t.Parallel()

runtime := NewInterpreterRuntime()

script := []byte(`
import Crypto

pub fun main() {
log(Crypto.SHA3_256.hash("01020304".decodeHex()))
log(Crypto.hash("01020304".decodeHex(), algorithm: Crypto.SHA3_256))
}
`)

called := false

var loggedMessages []string

runtimeInterface := &testRuntimeInterface{
hash: func(
data []byte,
hashAlgorithm string,
) []byte {
called = true
assert.Equal(t, []byte{1, 2, 3, 4}, data)
assert.Equal(t, "SHA3_256", hashAlgorithm)
return []byte{5, 6, 7, 8}
},
log: func(message string) {
loggedMessages = append(loggedMessages, message)
},
}

_, err := runtime.ExecuteScript(script, nil, runtimeInterface, utils.TestLocation)
require.NoError(t, err)

assert.Equal(t,
[]string{
"[5, 6, 7, 8]",
"[5, 6, 7, 8]",
},
loggedMessages,
)

assert.True(t, called)
}
10 changes: 10 additions & 0 deletions runtime/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ type Interface interface {
signatureAlgorithm string,
hashAlgorithm string,
) bool

// Hash returns the digest of hashing the given data with using the given hash algorithm
Hash(data []byte, hashAlgorithm string) []byte
}

type HighLevelStorage interface {
Expand Down Expand Up @@ -194,3 +197,10 @@ func (i *EmptyRuntimeInterface) VerifySignature(
) bool {
return false
}

func (i *EmptyRuntimeInterface) Hash(
_ []byte,
_ string,
) []byte {
return nil
}
1 change: 1 addition & 0 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ func (r *interpreterRuntime) loadContract(
inter,
constructor,
runtimeInterface,
runtimeInterface,
invocationRange,
)
if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type testRuntimeInterface struct {
signatureAlgorithm string,
hashAlgorithm string,
) bool
hash func(data []byte, hashAlgorithm string) []byte
setCadenceValue func(owner Address, key string, value cadence.Value) (err error)
}

Expand Down Expand Up @@ -287,6 +288,13 @@ func (i *testRuntimeInterface) VerifySignature(
)
}

func (i *testRuntimeInterface) Hash(data []byte, hashAlgorithm string) []byte {
if i.hash == nil {
return nil
}
return i.hash(data, hashAlgorithm)
}

func (i *testRuntimeInterface) HighLevelStorageEnabled() bool {
return i.setCadenceValue != nil
}
Expand Down
20 changes: 19 additions & 1 deletion runtime/stdlib/contracts/crypto.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ pub struct interface SignatureVerifier {
): Bool
}

pub struct interface Hasher {

pub fun hash(
data: [UInt8],
algorithm: String
): [UInt8]
}

pub contract Crypto {

pub struct SignatureAlgorithm {
Expand All @@ -33,6 +41,10 @@ pub contract Crypto {
init(name: String) {
self.name = name
}

pub fun hash(_ data: [UInt8]): [UInt8] {
return Crypto.hash(data, algorithm: self)
}
}

/// SHA2_256 is Secure Hashing Algorithm 2 (SHA-2) with a 256-bit digest
Expand All @@ -41,6 +53,10 @@ pub contract Crypto {
/// SHA3_256 is Secure Hashing Algorithm 3 (SHA-3) with a 256-bit digest
pub let SHA3_256: HashAlgorithm

pub fun hash(_ data: [UInt8], algorithm: HashAlgorithm): [UInt8] {
return self.hasher.hash(data: data, algorithm: algorithm.name)
}

pub struct PublicKey {
pub let publicKey: [UInt8]
pub let signatureAlgorithm: SignatureAlgorithm
Expand Down Expand Up @@ -196,10 +212,12 @@ pub contract Crypto {
priv let domainSeparationTagUser: String

priv let signatureVerifier: {SignatureVerifier}
priv let hasher: {Hasher}

init(signatureVerifier: {SignatureVerifier}) {
init(signatureVerifier: {SignatureVerifier}, hasher: {Hasher}) {

self.signatureVerifier = signatureVerifier
self.hasher = hasher

// Initialize constants

Expand Down
72 changes: 60 additions & 12 deletions runtime/stdlib/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type CryptoSignatureVerifier interface {
) bool
}

type CryptoHasher interface {
Hash(
data []byte,
hashAlgorithm string,
) []byte
}

var CryptoChecker = func() *sema.Checker {

code := internal.MustAssetString("contracts/crypto.cdc")
Expand Down Expand Up @@ -54,18 +61,15 @@ var CryptoChecker = func() *sema.Checker {
return checker
}()

var cryptoSignatureVerifierInterfaceType = CryptoChecker.GlobalTypes["SignatureVerifier"].Type.(*sema.InterfaceType)

var cryptoSignatureVerifierRestrictedType = &sema.RestrictedType{
Type: &sema.AnyType{},
Restrictions: []*sema.InterfaceType{
cryptoSignatureVerifierInterfaceType,
},
}
var cryptoContractType = CryptoChecker.GlobalTypes["Crypto"].Type.(*sema.CompositeType)

var cryptoContractInitializerTypes = []sema.Type{
cryptoSignatureVerifierRestrictedType,
}
var cryptoContractInitializerTypes = func() (result []sema.Type) {
result = make([]sema.Type, len(cryptoContractType.ConstructorParameters))
for i, parameter := range cryptoContractType.ConstructorParameters {
result[i] = parameter.TypeAnnotation.Type
}
return result
}()

func newCryptoContractVerifySignatureFunction(signatureVerifier CryptoSignatureVerifier) interpreter.FunctionValue {
return interpreter.NewHostFunctionValue(
Expand Down Expand Up @@ -118,7 +122,7 @@ func newCryptoContractVerifySignatureFunction(signatureVerifier CryptoSignatureV

func newCryptoContractSignatureVerifier(signatureVerifier CryptoSignatureVerifier) *interpreter.CompositeValue {
implementationTypeID :=
sema.TypeID(string(cryptoSignatureVerifierInterfaceType.ID()) + "Impl")
sema.TypeID(string(cryptoContractInitializerTypes[0].ID()) + "Impl")

result := interpreter.NewCompositeValue(
CryptoChecker.Location,
Expand All @@ -135,10 +139,53 @@ func newCryptoContractSignatureVerifier(signatureVerifier CryptoSignatureVerifie
return result
}

func newCryptoContractHashFunction(hasher CryptoHasher) interpreter.FunctionValue {
return interpreter.NewHostFunctionValue(
func(invocation interpreter.Invocation) trampoline.Trampoline {
data, err := interpreter.ByteArrayValueToByteSlice(invocation.Arguments[0])
if err != nil {
panic(fmt.Errorf("hash: invalid data argument: %w", err))
}

hashAlgorithmStringValue, ok := invocation.Arguments[1].(*interpreter.StringValue)
if !ok {
panic(errors.New("hash: invalid hash algorithm argument: not a string"))
}
hashAlgorithm := hashAlgorithmStringValue.Str

digest := hasher.Hash(data, hashAlgorithm)

result := interpreter.ByteSliceToByteArrayValue(digest)

return trampoline.Done{Result: result}
},
)
}

func newCryptoContractHasher(hasher CryptoHasher) *interpreter.CompositeValue {
implementationTypeID :=
sema.TypeID(string(cryptoContractInitializerTypes[1].ID()) + "Impl")

result := interpreter.NewCompositeValue(
CryptoChecker.Location,
implementationTypeID,
common.CompositeKindStructure,
nil,
nil,
)

result.Functions = map[string]interpreter.FunctionValue{
"hash": newCryptoContractHashFunction(hasher),
}

return result
}

func NewCryptoContract(
inter *interpreter.Interpreter,
constructor interpreter.FunctionValue,
signatureVerifier CryptoSignatureVerifier,
hasher CryptoHasher,
invocationRange ast.Range,
) (
*interpreter.CompositeValue,
Expand All @@ -147,6 +194,7 @@ func NewCryptoContract(

var cryptoContractInitializerArguments = []interpreter.Value{
newCryptoContractSignatureVerifier(signatureVerifier),
newCryptoContractHasher(hasher),
}

value, err := inter.InvokeFunctionValue(
Expand Down
6 changes: 3 additions & 3 deletions runtime/stdlib/internal/contracts.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.