From e74fbfea80c96d4427dc08a61f16e5ea6fe8544f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Thu, 17 Oct 2024 13:01:13 -0700 Subject: [PATCH 1/6] add Crypto contract --- contracts/Crypto.cdc | 172 +++++++++++++++++++++ lib/go/contracts/contracts.go | 28 ++-- lib/go/contracts/contracts_test.go | 6 + lib/go/contracts/internal/assets/assets.go | 23 +++ lib/go/test/crypto_test.go | 168 ++++++++++++++++++++ 5 files changed, 380 insertions(+), 17 deletions(-) create mode 100644 contracts/Crypto.cdc create mode 100644 lib/go/test/crypto_test.go diff --git a/contracts/Crypto.cdc b/contracts/Crypto.cdc new file mode 100644 index 000000000..631bf1709 --- /dev/null +++ b/contracts/Crypto.cdc @@ -0,0 +1,172 @@ + +access(all) +contract Crypto { + + access(all) + fun hash(_ data: [UInt8], algorithm: HashAlgorithm): [UInt8] { + return algorithm.hash(data) + } + + access(all) + fun hashWithTag(_ data: [UInt8], tag: String, algorithm: HashAlgorithm): [UInt8] { + return algorithm.hashWithTag(data, tag: tag) + } + + access(all) + struct KeyListEntry { + + access(all) + let keyIndex: Int + + access(all) + let publicKey: PublicKey + + access(all) + let hashAlgorithm: HashAlgorithm + + access(all) + let weight: UFix64 + + access(all) + let isRevoked: Bool + + init( + keyIndex: Int, + publicKey: PublicKey, + hashAlgorithm: HashAlgorithm, + weight: UFix64, + isRevoked: Bool + ) { + self.keyIndex = keyIndex + self.publicKey = publicKey + self.hashAlgorithm = hashAlgorithm + self.weight = weight + self.isRevoked = isRevoked + } + } + + access(all) + struct KeyList { + + access(self) + let entries: [KeyListEntry] + + init() { + self.entries = [] + } + + /// Adds a new key with the given weight + access(all) + fun add( + _ publicKey: PublicKey, + hashAlgorithm: HashAlgorithm, + weight: UFix64 + ): KeyListEntry { + + let keyIndex = self.entries.length + let entry = KeyListEntry( + keyIndex: keyIndex, + publicKey: publicKey, + hashAlgorithm: hashAlgorithm, + weight: weight, + isRevoked: false + ) + self.entries.append(entry) + return entry + } + + /// Returns the key at the given index, if it exists. + /// Revoked keys are always returned, but they have the `isRevoked` field set to true + access(all) + fun get(keyIndex: Int): KeyListEntry? { + if keyIndex >= self.entries.length { + return nil + } + + return self.entries[keyIndex] + } + + /// Marks the key at the given index revoked, but does not delete it + access(all) + fun revoke(keyIndex: Int) { + if keyIndex >= self.entries.length { + return + } + + let currentEntry = self.entries[keyIndex] + self.entries[keyIndex] = KeyListEntry( + keyIndex: currentEntry.keyIndex, + publicKey: currentEntry.publicKey, + hashAlgorithm: currentEntry.hashAlgorithm, + weight: currentEntry.weight, + isRevoked: true + ) + } + + /// Returns true if the given signatures are valid for the given signed data + access(all) + fun verify( + signatureSet: [KeyListSignature], + signedData: [UInt8], + domainSeparationTag: String + ): Bool { + + var validWeights: UFix64 = 0.0 + + let seenKeyIndices: {Int: Bool} = {} + + for signature in signatureSet { + + // Ensure the key index is valid + if signature.keyIndex >= self.entries.length { + return false + } + + // Ensure this key index has not already been seen + if seenKeyIndices[signature.keyIndex] ?? false { + return false + } + + // Record the key index was seen + seenKeyIndices[signature.keyIndex] = true + + // Get the actual key + let key = self.entries[signature.keyIndex] + + // Ensure the key is not revoked + if key.isRevoked { + return false + } + + // Ensure the signature is valid + if !key.publicKey.verify( + signature: signature.signature, + signedData: signedData, + domainSeparationTag: domainSeparationTag, + hashAlgorithm:key.hashAlgorithm + ) { + return false + } + + validWeights = validWeights + key.weight + } + + return validWeights >= 1.0 + } + } + + access(all) + struct KeyListSignature { + + access(all) + let keyIndex: Int + + access(all) + let signature: [UInt8] + + init(keyIndex: Int, signature: [UInt8]) { + self.keyIndex = keyIndex + self.signature = signature + } + } +} \ No newline at end of file diff --git a/lib/go/contracts/contracts.go b/lib/go/contracts/contracts.go index 59b22a199..c2b16c6f5 100644 --- a/lib/go/contracts/contracts.go +++ b/lib/go/contracts/contracts.go @@ -40,6 +40,7 @@ const ( flowContractAuditsFilename = "FlowContractAudits.cdc" flowNodeVersionBeaconFilename = "NodeVersionBeacon.cdc" flowRandomBeaconHistoryFilename = "RandomBeaconHistory.cdc" + cryptoFilename = "Crypto.cdc" // Test contracts // only used for testing @@ -193,8 +194,7 @@ func FlowIDTableStaking(env templates.Environment) []byte { // FlowStakingProxy returns the StakingProxy contract. func FlowStakingProxy() []byte { - code := assets.MustAssetString(flowStakingProxyFilename) - return []byte(code) + return assets.MustAsset(flowStakingProxyFilename) } // FlowStakingCollection returns the StakingCollection contract. @@ -223,16 +223,12 @@ func FlowLockedTokens( // FlowQC returns the FlowClusterQCs contract. func FlowQC() []byte { - code := assets.MustAssetString(flowQCFilename) - - return []byte(code) + return assets.MustAsset(flowQCFilename) } // FlowDKG returns the FlowDKG contract. func FlowDKG() []byte { - code := assets.MustAssetString(flowDKGFilename) - - return []byte(code) + return assets.MustAsset(flowDKGFilename) } // FlowEpoch returns the FlowEpoch contract. @@ -246,23 +242,17 @@ func FlowEpoch(env templates.Environment) []byte { // NodeVersionBeacon returns the NodeVersionBeacon contract content. func NodeVersionBeacon() []byte { - code := assets.MustAssetString(flowNodeVersionBeaconFilename) - - return []byte(code) + return assets.MustAsset(flowNodeVersionBeaconFilename) } func RandomBeaconHistory() []byte { - code := assets.MustAssetString(flowRandomBeaconHistoryFilename) - - return []byte(code) + return assets.MustAsset(flowRandomBeaconHistoryFilename) } // FlowContractAudits returns the deprecated FlowContractAudits contract. // This contract is no longer used on any network func FlowContractAudits() []byte { - code := assets.MustAssetString(flowContractAuditsFilename) - - return []byte(code) + return assets.MustAsset(flowContractAuditsFilename) } /******************** Test contracts *********************/ @@ -331,3 +321,7 @@ func TestFlowFees(fungibleTokenAddress, flowTokenAddress, storageFeesAddress str return []byte(code) } + +func Crypto() []byte { + return assets.MustAsset(cryptoFilename) +} diff --git a/lib/go/contracts/contracts_test.go b/lib/go/contracts/contracts_test.go index 2b568da19..c42e7095b 100644 --- a/lib/go/contracts/contracts_test.go +++ b/lib/go/contracts/contracts_test.go @@ -89,3 +89,9 @@ func TestNodeVersionBeacon(t *testing.T) { contract := contracts.NodeVersionBeacon() assert.NotNil(t, contract) } + +func TestCrypto(t *testing.T) { + contract := contracts.Crypto() + + assert.NotNil(t, contract) +} diff --git a/lib/go/contracts/internal/assets/assets.go b/lib/go/contracts/internal/assets/assets.go index 03e9775ca..31f66f325 100644 --- a/lib/go/contracts/internal/assets/assets.go +++ b/lib/go/contracts/internal/assets/assets.go @@ -1,5 +1,6 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: +// Crypto.cdc (4.588kB) // FlowFees.cdc (9.634kB) // FlowIDTableStaking.cdc (101.556kB) // FlowServiceAccount.cdc (8.509kB) @@ -82,6 +83,26 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } +var _cryptoCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x57\x5d\x6f\xdb\x36\x14\x7d\xf7\xaf\xb8\x7b\x73\x30\x43\xcd\x80\x61\x18\x04\xa8\x41\xb7\x75\x5b\x90\x0d\x18\x92\x16\x7d\x30\x8c\x96\x91\xae\x24\xc2\x2a\x65\x90\x57\x76\x84\xc0\xff\x7d\xa0\x68\x49\x24\x45\x7f\xa4\x48\xf5\x62\xc9\x3a\xf7\xeb\xf0\xdc\x2b\x72\xc6\xd2\x14\x95\x9a\xb3\xaa\xba\x9a\xa5\xb5\x20\xc9\x52\x82\xdf\x65\xbb\xa1\x1a\x9e\x67\x33\x00\x00\x1b\xa2\x9f\xf3\x46\x40\xc9\x54\x39\xff\x0c\x19\x23\x16\xc3\xf2\xe3\xad\xa0\x5f\x57\x0b\x60\x55\x51\x4b\x4e\xe5\xd7\x18\xfe\x66\xaa\x7c\xd7\x3f\x5e\x0d\x18\x78\xee\x5c\xe8\x4b\x22\x35\x52\x8c\x36\x51\xe7\x53\x7b\x34\x61\xf6\xa7\xa3\x7f\xe2\x54\x7e\x60\xc5\x34\x09\x62\x45\x0c\x0f\x24\xb9\x28\x5e\x23\xa3\x3e\x8e\x8e\x72\x70\x4e\xac\x38\x99\xa2\x22\xd9\xa4\x04\x77\xd8\xfe\xc3\x15\xbd\x17\x24\xdb\x9e\xcb\x10\x5c\x5f\x15\x12\xac\xb1\xbd\x15\x19\x3e\xc5\x70\x2b\xe8\x3c\x7c\xd3\x3c\x56\x3c\xbd\xc3\x36\x86\xff\xfa\xdb\xf3\x56\xa5\xcd\x82\x47\xca\x79\xeb\x1d\xf2\xa2\xa4\x18\x3e\xfe\xc9\x9f\x7e\xf9\xf9\x3c\x9e\xab\x7b\xdc\xd6\x6b\xcc\x62\xf8\xad\xae\xab\xd1\x80\x0b\x4e\xf3\xe1\x49\x5f\x4e\xf5\x0b\xe7\x55\xa8\x52\x17\x71\xaa\x2a\x17\xe9\x56\xe0\xbe\xf3\xb3\xed\xff\xbf\xb2\x34\xd2\xad\x2f\x56\x79\xd4\xe7\x0b\xc9\x90\xfa\x14\x34\x64\x0e\xc9\x58\xc5\x14\xe6\xa4\x0f\x89\x5b\xce\x14\x6e\x6a\x80\xe4\x50\xcc\x14\x30\x14\x02\xc9\x58\xd4\x00\xdb\x5f\xae\xdd\x80\x6c\x75\x00\x77\x91\x51\x90\xe4\xa8\x62\x58\xda\x8a\x5f\x79\x8b\x1d\x24\xf1\x60\x0a\x09\x2c\x57\x56\x7e\xc3\xed\x9b\x37\x6f\xe0\x5d\x96\x29\x60\x20\x70\xa7\x89\x86\x1d\xa7\x12\xa8\x44\x28\xf8\x16\x85\x4f\x41\x48\x88\x7a\x62\xb0\x2c\x73\xe5\xf6\xf9\x3b\xaa\x6a\x14\x4e\x7c\x74\x08\x80\xd7\xf4\x90\x38\x8c\x44\x15\x8a\x82\xca\x09\x1c\x3b\x3f\x89\xe3\xd6\x2d\x0c\x9c\x5e\xea\xef\x16\x13\x8c\x55\xfe\x26\x5c\x7e\x80\x82\xf2\x38\x05\x36\x0d\xe6\x77\xfa\xde\x6a\xb0\x9c\x55\x0a\x1d\xc0\xd5\x51\x79\x44\x6c\xb3\x41\x91\xcd\xbb\xe2\x5d\xd8\x61\x64\x77\x6f\x8e\x09\xe8\xbe\xc3\xa8\x4e\x34\x5a\x41\x8c\x2c\xfd\xf0\x8e\x1d\xe0\x39\x70\x02\x7c\xe2\x8a\x54\xe4\x59\x9b\x4e\x5a\x63\xab\x80\x49\x04\x56\xed\x58\xab\x0e\x91\x31\x5b\xc0\x63\xd3\x39\x6c\xa1\x64\x5b\xec\x5c\x7f\x19\x0a\xfd\x02\x39\xc7\x2a\x03\x85\x04\x54\x03\xc9\x06\xcf\x6a\xb5\x40\x9a\x3b\xd3\xd0\x93\xd1\x8d\xd7\x49\x3c\x1f\x55\xf4\x36\x28\x23\xcf\xc0\x22\x4e\xf0\xca\x79\xb5\x9f\x85\xe8\xb5\x5d\x2e\xfb\x58\x47\x1b\xf6\x5f\x26\xd7\xa7\xd8\x06\x69\xb8\x31\xcc\x65\x35\x2a\x10\x35\x41\x86\x15\x12\x02\x3f\xdf\xcc\xc6\xde\xe3\xe8\xf5\x48\x39\x45\x88\xee\xc1\xb4\x91\x12\xc5\xa1\xa5\x93\x73\xe4\x80\x27\xe6\x11\xf2\x82\x36\xb6\x43\x46\x17\xf5\xb4\x63\x71\x79\x83\x3b\x66\x17\x76\xbb\x63\x73\x41\xeb\x3b\x4d\x00\x4e\xe7\x1f\xeb\x5c\xd9\xa0\x5e\xd1\x51\x48\x8a\x17\x82\x51\x23\xd1\xf4\xe4\x96\x55\x3c\x83\xbc\x96\x1e\x04\xb3\x6e\x77\x78\x56\x51\x5b\x94\x3c\xf7\x56\x60\x08\xf1\x80\x34\x7e\xdb\x1e\xfa\x7f\x57\x8b\x09\x1a\xb3\x3f\x9c\xad\xa8\x03\xc8\xea\xaf\x8c\x8b\x07\xdc\x30\xc9\x88\xd7\xe2\xc3\xb8\x4d\xb5\xbf\x17\x7a\xe3\xe1\x7f\x27\xb6\x4c\x9a\x0a\x3f\x75\xe4\xaa\xfe\x3b\x03\x09\x5c\x47\xd7\x53\x81\x2a\x44\x71\xd7\x89\x84\xa7\xfa\xbb\xfc\x7c\x2b\xc8\x78\xde\x43\x02\xcf\x9e\xa4\x35\x6b\x43\xad\xc0\x85\x53\xb8\x9f\x8a\x59\x19\x78\x2f\x94\x06\xf7\x3d\x6e\xda\x9a\x2b\x93\xe5\x74\xed\xf3\xd1\x67\xf4\xf2\xae\x84\x71\x10\x4d\xbf\x1a\x30\xed\x51\x3f\x47\xae\xac\x24\x4b\x66\xa6\x0d\xab\x24\xb2\xac\x85\x47\xd4\x52\x41\x14\xc1\xac\x1d\x22\x97\xd3\x22\x56\x70\x73\x63\x92\x7a\xbd\xbc\xef\x31\xad\x65\xe6\x71\xbb\x63\x2a\x9c\xe5\x05\x29\x26\xa6\xe3\x42\xb1\xfe\x42\x33\x9d\x59\x4a\x0d\xab\x74\xbc\x09\xea\xb0\x49\xf1\x07\x5d\x20\xd0\x45\x4a\x31\xec\x4b\x6f\x4b\x6a\x71\xbe\xc6\xd6\xda\xbf\xbe\xbe\x1a\xd0\x16\xfb\x09\xc5\xfe\xa0\x13\x19\x06\x67\x14\x9a\x10\xc3\x1a\xf4\x0e\x63\x4b\xe7\xc3\xdd\x74\x16\x82\x37\x2f\xc6\xfb\x30\x36\x38\x3a\x02\x7f\x86\xad\xdd\x09\xaf\xab\x3a\x7e\xbc\x80\xc0\x69\xa7\xbf\x5e\x4a\xba\x3d\xb1\x20\x71\x1f\x7f\xec\x96\x39\x70\x7c\x09\xef\x3f\x1c\xdb\xb7\x09\xfc\x14\x5d\x7f\xcb\x69\x66\x98\xdd\xdf\xe1\x34\x6e\x69\xe0\x30\xfe\xbd\xf3\x8f\x7b\xbe\x0d\xe0\xbf\xfd\x94\x39\x0a\x3a\x19\xfd\x4e\x08\xda\xff\x1f\x00\x00\xff\xff\xc0\xa0\xe3\x76\xec\x11\x00\x00" + +func cryptoCdcBytes() ([]byte, error) { + return bindataRead( + _cryptoCdc, + "Crypto.cdc", + ) +} + +func cryptoCdc() (*asset, error) { + bytes, err := cryptoCdcBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "Crypto.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb3, 0x3c, 0x3a, 0xd, 0x4a, 0x76, 0xb6, 0x38, 0x96, 0x40, 0x54, 0x48, 0xce, 0x1f, 0xa6, 0x53, 0x31, 0xb4, 0x7a, 0x22, 0xd9, 0x8, 0xea, 0xd9, 0x28, 0x4, 0xc, 0x5f, 0x75, 0x8c, 0x39, 0xc0}} + return a, nil +} + var _flowfeesCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x5a\x5f\x73\xdb\x36\x12\x7f\xf7\xa7\xd8\xfa\xa1\x47\xb6\xb2\x9c\x87\x9b\x7b\xf0\x58\x69\x1d\xd7\xbe\xe9\xcc\xdd\x5c\x26\x75\xdd\xc7\x0c\x4c\x2e\x45\x4c\x28\x40\x07\x80\x92\xd5\x8c\xbf\xfb\x0d\xfe\x12\x20\x41\x5b\xf6\xa5\x7e\x88\x22\x72\x01\xec\xdf\xdf\x2e\x76\x45\x37\x5b\x2e\x14\xdc\xf6\x6c\x4d\x1f\x3a\xbc\xe3\x5f\x90\x41\x23\xf8\x06\x4e\x93\x67\xa7\x27\x9e\xb2\xe3\xfb\x84\xca\x7f\x4f\x28\x7e\x53\x5c\x90\x35\xde\x22\xca\x88\x2e\x7a\x7a\x7a\x72\x42\xaa\x0a\xa5\x2c\x48\xd7\x95\x50\x71\xa6\x04\xa9\xec\x62\xb3\xea\xeb\xc9\x09\x00\xc0\xf9\x39\xdc\xec\x90\x29\x50\x2d\x51\x40\x25\xe0\x86\x2a\x85\x35\xec\x5b\x64\xa0\xf4\xc1\x12\x88\x40\xa8\x71\xcb\x25\xd5\x6f\x14\x07\xd5\x22\x34\x88\xb0\x23\x7d\xa7\xcc\x3e\xf1\x61\x68\x36\x34\x4c\xcb\x5f\xfc\xb2\x82\x6c\x78\xcf\xd4\x05\xfc\x7e\x4b\x1f\xff\xf1\xf7\xf2\x95\xc7\xef\xa9\x6a\x6b\x41\xf6\x4e\x2d\xc7\x33\xf0\x87\x5f\xf8\x16\x06\x1a\xad\x29\x2b\x7d\xdd\x57\x0a\xeb\x99\xa3\xb4\x46\x7f\x71\x24\xa3\x73\x16\x40\x59\xd5\xf5\x92\x72\x76\xd3\x34\x5c\x44\x2f\xf0\x11\xab\x5e\x4d\x5e\x1c\xcb\x19\x6c\x89\x20\x1b\x54\x28\x24\x54\x2d\x61\x6b\x9c\xe7\xee\x63\x20\xbd\x36\x94\x75\x21\x7b\xb1\xc6\x5b\x52\x29\x2e\x66\x59\xbd\xe6\x72\x9e\xdd\xf8\xe5\xc0\xf2\x47\x41\x77\x44\x39\xc3\x18\xab\xc1\xb6\x7f\xe8\x68\xe5\x1d\x08\x9a\x9e\x55\x7a\x97\x98\x59\x89\x5d\x53\xc2\x8e\x08\xbb\xee\x02\x7e\x0e\x6e\xbf\xbc\x37\x26\x9e\x88\xd6\xf4\xcc\x6f\x59\x68\x97\xb8\x80\x9f\xbf\x26\x11\x65\x17\x3e\x95\xf0\xd5\xac\xd5\x7f\x1d\x2a\xeb\x3e\x97\x67\xf6\x93\xc8\xef\xa6\x47\xc5\xd4\x0f\xa4\x23\xac\x42\x58\x19\xfa\xa5\xfb\x1a\x48\x34\xdf\x4b\xc3\xf2\x32\xe5\xe5\xf2\x4c\x7f\x96\x81\x50\xdb\x6e\x36\x22\xdc\xae\x96\xfa\xc9\xab\xf2\x1c\xfe\x89\xca\x38\xba\x67\x82\x37\xe6\xab\x09\xdf\xfb\xac\xe3\x6b\xa5\xac\x51\xdd\x22\x7e\xb0\x6b\x8a\xd2\x9b\x28\x52\x83\x40\xd5\x0b\x16\x33\x1f\xcb\xf5\x34\xd5\xb5\x40\xc9\x7b\x51\x21\x5c\xd5\x1b\xca\xa8\x54\x82\x28\x2e\xa2\x1d\xcf\xcf\x43\x80\x46\xcf\xe2\xd7\x57\x5d\xc7\xf7\xd2\xf0\x4f\x92\x4d\x14\x0f\x4b\x7d\xbc\xcf\x44\x78\x4e\x58\xbf\xd4\xaa\xf6\x56\xf0\xcd\x2d\xa2\xd1\xcd\x38\xdc\xe7\xfc\x23\x92\xc2\x1b\xdd\xfa\xee\xe5\x59\x00\x4b\xa7\x25\x7f\x58\xd8\xda\x7e\x96\xc9\x06\x91\xa5\xa7\xd0\x93\x5b\xe0\xac\x71\x79\x96\x4a\xea\xcc\xe0\x7d\xe1\x39\xfd\xd9\xe0\x07\xd2\x75\x41\x6b\x11\x34\x10\x05\x3c\xf6\xd9\xb1\x0a\xa5\xf1\x97\x01\x20\xbe\x31\x32\x64\xf4\xcb\x70\x3f\x1c\x07\x2b\x78\xe6\xf8\xe8\xcb\x0c\x0f\x99\x87\x33\x0c\x65\x1e\xa6\x96\x08\xe6\x9e\xa8\x24\xe1\xb8\xfc\x3f\x6c\xe4\xed\x63\xe4\x82\xc6\x08\xf6\x82\x65\x7e\x1b\x54\x50\x7c\x86\x8c\x71\x72\x2a\xe6\x5d\x9d\xaa\xd8\x8b\xb6\x1e\x8b\x56\xfe\xb5\xe6\x49\x18\x59\x1e\x6f\xac\x74\xdd\x5f\x61\x3a\x48\xc1\xf6\x0a\xa4\x12\x7d\xa5\xa0\xe5\x5d\x4d\xd9\x3a\x17\x4a\x0c\xb1\xb6\x05\x50\x45\xba\xaa\xef\x74\xa2\x73\x64\x72\x02\x9a\x6e\xbb\x84\x9b\x04\x32\xcf\xe1\xae\x4d\x3d\x41\x67\xf9\x5e\xda\x13\x36\xe4\x0b\x82\x12\x84\x49\x62\xd2\xa5\xad\x45\x04\xca\x2d\x67\x86\xa0\xa5\xeb\x16\x3a\x4e\x6a\x09\x9c\x19\x36\x18\xaa\x3d\x17\x5f\xb2\xfe\xa4\x73\x6b\xc6\x77\x26\xec\xdc\xfe\xeb\x3f\x7f\x40\xc5\xa5\xd2\xd9\x86\x33\x84\x9e\x51\xf3\xff\x60\x3a\x40\x63\x83\xa5\x25\xbf\xff\xb7\x66\xda\xb2\x25\x35\xb6\x42\xc3\x05\x18\x69\xb5\x12\x27\xab\x66\x99\x7b\x06\x60\x8e\x67\x32\xf8\xc9\xab\x98\x9c\xac\x9a\x65\xf2\x19\xa0\x1b\xa0\x80\x32\xaa\xbe\x31\x88\xa6\x01\x6e\x12\x77\x74\x00\xac\x62\xd3\x4e\x49\x33\xc7\xc2\x2a\xc7\xcc\x74\x69\x86\x29\x58\xe5\x58\x9d\x0b\x2c\xb8\x47\x41\x9b\xc3\x47\x72\x40\xe1\x6a\x92\x4f\x28\x75\x7e\x35\x16\xd1\xa9\x0f\x6b\x78\x38\x18\x07\xde\x0d\xb4\xd2\x11\xdf\x72\x71\x37\x04\xc1\x4d\xb0\x54\xae\x8a\x8c\xc3\x6e\xf6\xd8\xa4\x68\xb9\x13\x3d\x02\xb5\x65\xd5\x56\xd3\x42\x4b\x24\xc8\xbe\x69\x68\x45\x75\xe9\xec\x4b\x2f\xed\x2f\x9a\x28\x0e\xc8\xc1\x6d\x34\x20\x70\xa6\x28\xeb\xf3\x69\x56\xa3\x6a\x45\x1c\xf7\x18\xc9\x73\x01\x1f\x38\xef\x12\x8e\x5a\x04\x9d\x3a\x36\xfd\xc6\x71\xe4\x59\x10\xf8\xdf\x9e\x0a\xac\x8f\xe7\x65\x99\x6e\x4c\x25\xec\x48\xa7\x05\x96\x50\x63\x43\x9d\xe2\x8f\x57\x7a\x3e\x2e\xb4\x70\x9e\x37\xb7\x3e\x13\xb8\x56\x30\xf2\x68\x04\x9b\xc0\x5a\x31\xe0\x84\xf9\xfe\x63\x24\x90\x7e\x50\x4e\x04\xae\x08\xd3\x1e\xdc\x8b\xf8\x8c\x82\x36\xa6\x0a\x22\x3b\x42\x3b\xa2\xe3\x7c\x1c\xda\x1e\x61\xcb\x59\x51\x1c\x8f\x91\xfc\x3a\xaf\xcc\x44\xf9\x33\x46\x5d\xcc\x29\x65\x01\x2f\x9c\x91\x8b\xf6\xec\x41\xb0\xca\x7b\xd5\x74\xf9\x88\x15\x58\x8d\x99\x9b\x2e\xc9\xf3\x08\xab\x19\xe6\xc7\x35\xd1\x00\x00\xaf\x08\x6a\x2a\x75\x62\xed\x06\x40\xd0\xc8\xfd\x80\x0d\x17\xc1\x92\x6c\x0d\x24\x76\x84\xa5\x3f\xe6\x57\x65\x4f\xa2\x28\xed\x8d\x79\xec\x31\x26\x9a\xfe\x26\x43\x3c\x51\x69\x53\x28\x32\xde\xaf\xdb\x38\x6e\xf2\x91\xb5\xf0\x27\x11\x56\x3b\xe4\xb2\xb5\x9e\xf7\xea\x2d\x97\x36\xb9\x8c\xdd\x3b\xf0\x58\x90\xaa\xe2\xc2\x56\x17\xb6\x89\x32\xce\x8f\x66\x73\xbf\xe1\xc4\x79\xdd\x05\x30\xda\xbf\xf4\x7b\x9f\x44\x61\x36\xb6\x76\x24\xb1\x8f\x7b\xe2\x58\x77\x48\xa3\xf9\xea\x23\xb8\xfb\xd1\x6f\xf7\xf6\x90\xd5\xf7\x8e\x0d\x79\x9c\x08\xe1\x38\x1e\xd7\xba\xc7\x7b\x49\x11\x5c\xed\xb3\xb5\xe9\x55\x55\xe9\xab\x55\xaf\xda\xe2\x03\x17\x82\xef\xef\x35\xca\x95\xf0\xfd\x95\x95\x6a\x11\xc5\x6c\xbe\x13\x13\x08\x36\xe4\xf1\x26\xdf\x94\x31\x24\xe5\xc5\x91\x79\xc5\xdf\xdc\xbd\xf6\x5c\xed\x36\x0f\xe0\x03\xb4\xea\x32\x63\x43\x1e\xd3\xf0\xd2\x19\xde\xc0\x00\xdf\x6c\x7b\x65\x1a\x7c\xc5\x44\x98\xd1\x83\x4c\x7f\x69\x2a\x5e\x79\x92\x65\xdb\xb9\x45\xe0\x78\x1c\x3b\xcf\x4a\x60\x50\xd4\xee\xf0\x69\x82\x3b\xa3\x36\xe5\xb2\xc6\x46\x5f\x7b\xcd\x85\xf9\x13\x4a\x14\xbb\x40\x5d\x04\xeb\x2e\x49\x5d\x0b\x94\x32\xe2\x96\x36\xb3\x67\xac\x9c\xc9\x8a\x77\xe3\x9b\x91\x86\x09\x1b\x42\x41\xb2\x28\x36\xfe\x44\xc1\x01\x1f\xa9\x02\x24\xa2\x3b\x2c\x73\xb7\xf4\x39\xf3\x17\x09\xb1\xfe\x9b\x49\x0d\x4a\xf4\xb8\x98\x10\x4f\x52\x45\x5e\xb6\xe9\xc2\xb9\x4c\x32\xf1\xa0\x64\xe5\xcc\x0d\x76\xae\xe1\x64\xec\x60\xb0\xc3\x34\x46\x6c\x33\x24\x5e\xf7\xab\xbd\x7f\x60\xbd\x46\xa8\x88\x44\xd8\xb7\x28\x30\xaa\xaa\x6a\x8e\x92\x29\x68\xc9\x0e\x81\xd8\x0d\x16\xa0\x04\x92\xf4\x3c\x22\xe1\x5d\x1a\x09\x43\xff\xed\xdd\xf2\x5d\x6c\x7c\xed\x64\xa6\x5d\x64\xba\x38\xb0\x1a\xa0\x60\x29\xad\x7b\x2d\x1f\x0c\x18\x5c\x7e\x3f\xea\xef\xbd\x77\x2d\xba\x73\x47\x77\xde\xf8\xf7\xe6\xf5\xd8\x67\x06\x0e\x86\xe3\x26\x7d\xc0\x48\x8d\xaf\x72\x14\x07\xd8\xe3\xc2\xe6\xc1\xa7\x3b\xac\x81\xaa\xd8\x06\x91\xbb\xae\xb5\xfa\x50\xc7\x21\x61\xf9\xa8\x75\xc4\xa9\x23\xcf\xf8\xa5\xdf\xf8\xfd\xea\x28\xdf\x7b\x93\xc3\x1e\xed\xac\x93\x4e\xe8\xb5\xad\x0a\x4c\xe7\x3b\x49\xff\x5a\x11\xb2\x7f\x70\x9d\x71\xc5\x5d\xa3\xde\xdf\xca\xc3\x06\xa1\xa5\x78\xd5\xab\xd6\xe5\x05\x5b\x26\x0c\xab\x69\xbe\x9f\x6a\x77\x4c\x19\x2c\x3e\x6b\xb2\xe7\xb3\xce\xeb\xfb\xfe\x91\xe7\x69\xdf\x6f\x10\xaf\x4c\xbf\xf0\x1b\xa1\x3f\xce\x42\x3f\x6d\xe2\xc3\x8e\xc0\x4f\x81\x66\x2a\xc2\xb8\xcd\x6c\x41\xef\x0b\xa8\x39\x30\xae\x42\x29\xb5\x18\xaf\x97\x3c\x54\x67\xc3\x26\x3d\x63\xa8\x95\x4e\x04\xed\x0e\x61\xce\x61\x86\x17\x32\x83\xc2\xb9\xb0\x9b\xe0\x01\xc9\x40\x81\xb1\x56\xda\x04\xf6\x3d\xda\x12\x5e\x8b\x13\x09\x63\x3f\xfd\x04\x5b\xc2\x68\x55\x9c\xfe\xce\xcc\xc5\x43\x71\xb0\x87\x82\xc0\x06\x05\xea\xd8\x72\x35\x9f\xc7\x51\xc3\xae\x05\xc3\xd3\xf2\x64\xc6\x1a\xef\x33\xb0\x93\xb1\x8a\x45\x80\xa6\x57\xbd\xc1\x5d\x5d\x48\xf3\x5a\xa3\x86\x6a\x61\x4f\xbb\x0e\x18\xee\xf4\x5d\x52\x67\x3e\x52\xb5\x58\x4f\x0c\x43\xa4\xc7\x18\x63\x1f\x6d\x99\x9e\xd5\x28\x42\x6d\xe8\xe1\xc5\x33\x61\xb7\xe5\x0e\xd5\x55\x8b\x54\xc4\x91\x29\xad\x47\xd6\x3a\xb0\x74\xd0\x56\xbc\xeb\x70\x7a\x3d\x89\xa6\x04\x8e\x91\xeb\x5e\x08\x64\xaa\x3b\x58\x39\xa8\x34\x87\xf8\xb6\x58\x43\x68\x37\x29\xed\x0b\x22\xc3\x94\xcc\x66\x77\xd7\xe9\xa1\x1d\x55\x07\x9f\xc6\xe6\x8a\xdd\x8e\xaf\x69\x55\x4e\x14\xf2\xa1\x57\x71\x3f\x4e\x68\x07\xc7\x5c\x7a\xdc\x39\x6b\xea\x14\xb5\xf0\xdd\x04\xfb\xb0\x25\x12\x3a\x94\x32\xba\x91\xfa\xc5\x01\xb6\xf3\xb7\x05\xff\x17\x83\xc0\x71\x09\xc8\x0c\xb9\xdc\x10\x04\x2e\xcf\xe2\x55\x93\x01\x46\xd8\xbd\x3c\x6a\xa4\xe5\x76\x4d\x8b\xc6\x3b\xd7\x27\xf5\x2d\xd1\x28\x89\x09\xac\x38\xb3\x0d\x19\xac\xa1\x97\xbe\xad\x5a\x13\x45\x3c\x24\x53\xe9\xa6\x94\xfa\xe6\xe3\xc6\x5b\x1f\x93\xe9\x85\x51\x79\xc7\xab\x2f\x6e\x28\xac\x2b\x0d\xb3\xa2\x25\xdb\x2d\x32\x37\x94\x05\x3f\x7b\xc9\xce\x62\x83\xa0\x19\x58\x7e\x03\x72\x42\x7e\x4e\xb6\xa3\xb8\x8f\x66\x70\x71\x97\xfd\x62\xb6\x11\x1c\x4f\xe3\x9c\x6b\x06\xdc\xaa\xf8\xf6\x70\x99\x2c\x9c\xa0\x92\x41\xad\xc7\x84\xa6\x8c\xe0\xe8\x46\x08\x2e\x34\x3f\xe6\xea\xac\x1e\x47\x2d\x6d\xd3\x20\x3d\x98\xc6\xb6\x81\x2c\x34\x7d\x0d\x4a\x3a\xfa\xa7\xbe\xa8\x50\x21\xd5\x77\xa7\x39\x79\xed\xc4\x36\x3b\x3f\xfa\xac\xcf\x18\xbe\x8f\x24\x2f\xd3\x9b\x12\x6e\xb6\xea\x00\x4e\x18\x7f\xd7\xdf\x0b\x6a\xd8\x65\xb8\x1f\xa9\x4d\x71\x9f\xa6\x83\xaf\x8e\x75\xd6\x71\x52\xbf\x41\x67\xcf\x6f\x2a\xc9\x0e\x8b\x44\xac\x85\xe2\xc7\xee\xe8\xdd\xf2\x85\x21\x7c\xb2\xfd\xf2\xe5\xd1\x4a\x4a\x7f\xfc\x68\x25\x5d\x37\x3b\x5a\x79\x3a\x09\x2d\x1c\x57\x78\x64\xe1\xca\xce\xf8\xf5\x9b\xca\x02\xf7\x64\xfe\xe8\xe2\x7a\x4d\x77\xc8\xc6\x92\x98\xb7\x23\x16\xe6\xc3\xea\xd9\x02\xe8\xa5\xea\x2a\x33\x08\xd7\x50\x69\x38\x95\xbe\xc4\xca\x8c\xc7\x46\x25\x86\x22\x9d\x6b\x84\xd9\x95\x49\x23\xfe\x07\x28\x26\x12\xfe\xe0\x09\x73\x6d\xf8\x1f\xc7\xec\x0e\xe4\xb9\xd6\x7b\x39\x46\x8d\xc0\x4f\x1c\xa2\xa6\x35\x39\x8a\xb3\x6b\x73\x5b\x00\x62\x63\x2a\xfc\xc6\xc8\x66\x09\x6d\x04\xed\xe1\xfa\xb2\x41\x99\x8f\xc6\x4c\x4a\xf0\x43\x71\x5b\x26\x55\x66\xcf\x1b\x1d\xc1\x76\xe4\x6e\x68\xee\x0e\x5b\xbc\x00\xfd\xef\xe5\xf8\xa7\x15\xef\x8b\xb2\xcc\xff\xe6\x22\x51\xb2\x19\x9e\xea\xa3\xec\x01\xe9\xaf\x0e\x8a\x63\x22\xf5\xf2\xcc\xec\xb1\x80\x24\x48\x1b\x37\x24\x34\xfb\x79\x27\x7f\xfa\x5f\x00\x00\x00\xff\xff\x35\xea\xb4\x22\xa2\x25\x00\x00" func flowfeesCdcBytes() ([]byte, error) { @@ -453,6 +474,7 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ + "Crypto.cdc": cryptoCdc, "FlowFees.cdc": flowfeesCdc, "FlowIDTableStaking.cdc": flowidtablestakingCdc, "FlowServiceAccount.cdc": flowserviceaccountCdc, @@ -515,6 +537,7 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ + "Crypto.cdc": {cryptoCdc, map[string]*bintree{}}, "FlowFees.cdc": {flowfeesCdc, map[string]*bintree{}}, "FlowIDTableStaking.cdc": {flowidtablestakingCdc, map[string]*bintree{}}, "FlowServiceAccount.cdc": {flowserviceaccountCdc, map[string]*bintree{}}, diff --git a/lib/go/test/crypto_test.go b/lib/go/test/crypto_test.go new file mode 100644 index 000000000..36be9faa9 --- /dev/null +++ b/lib/go/test/crypto_test.go @@ -0,0 +1,168 @@ +package test + +import ( + "context" + "encoding/hex" + "fmt" + "testing" + + "github.com/onflow/cadence" + jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/flow-go-sdk" + sdktemplates "github.com/onflow/flow-go-sdk/templates" + "github.com/onflow/flow-go-sdk/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-core-contracts/lib/go/contracts" +) + +func TestCrypto(t *testing.T) { + b, adapter := newBlockchain() + + accountKeys := test.AccountKeyGenerator() + + // Create new keys for the Crypto account and deploy + cryptoAccountKey, _ := accountKeys.NewWithSigner() + cryptoCode := contracts.Crypto() + + cryptoAddress, err := adapter.CreateAccount( + context.Background(), + []*flow.AccountKey{cryptoAccountKey}, + []sdktemplates.Contract{ + { + Name: "Crypto", + Source: string(cryptoCode), + }, + }, + ) + assert.NoError(t, err) + + script := []byte(fmt.Sprintf( + ` + import Crypto from %s + + access(all) + fun main( + rawPublicKeys: [String], + weights: [UFix64], + domainSeparationTag: String, + signatures: [String], + toAddress: Address, + fromAddress: Address, + amount: UFix64 + ): Bool { + let keyList = Crypto.KeyList() + + var i = 0 + for rawPublicKey in rawPublicKeys { + keyList.add( + PublicKey( + publicKey: rawPublicKey.decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ), + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: weights[i], + ) + i = i + 1 + } + + let signatureSet: [Crypto.KeyListSignature] = [] + + var j = 0 + for signature in signatures { + signatureSet.append( + Crypto.KeyListSignature( + keyIndex: j, + signature: signature.decodeHex() + ) + ) + j = j + 1 + } + + // assemble the same message in cadence + let message = toAddress.toBytes() + .concat(fromAddress.toBytes()) + .concat(amount.toBigEndianBytes()) + + return keyList.verify( + signatureSet: signatureSet, + signedData: message, + domainSeparationTag: domainSeparationTag + ) + } + `, + cryptoAddress.HexWithPrefix(), + )) + + // create the keys + keyAlice, signerAlice := accountKeys.NewWithSigner() + keyBob, signerBob := accountKeys.NewWithSigner() + + // create the message that will be signed + addresses := test.AddressGenerator() + + toAddress := cadence.Address(addresses.New()) + fromAddress := cadence.Address(addresses.New()) + + amount, err := cadence.NewUFix64("100.00") + require.NoError(t, err) + + var message []byte + message = append(message, toAddress.Bytes()...) + message = append(message, fromAddress.Bytes()...) + message = append(message, amount.ToBigEndianBytes()...) + + // sign the message with Alice and Bob + signatureAlice, err := flow.SignUserMessage(signerAlice, message) + require.NoError(t, err) + + signatureBob, err := flow.SignUserMessage(signerBob, message) + require.NoError(t, err) + + publicKeys := cadence.NewArray([]cadence.Value{ + cadence.String(hex.EncodeToString(keyAlice.PublicKey.Encode())), + cadence.String(hex.EncodeToString(keyBob.PublicKey.Encode())), + }) + + // each signature has half weight + weightAlice, err := cadence.NewUFix64("0.5") + require.NoError(t, err) + + weightBob, err := cadence.NewUFix64("0.5") + require.NoError(t, err) + + weights := cadence.NewArray([]cadence.Value{ + weightAlice, + weightBob, + }) + + signatures := cadence.NewArray([]cadence.Value{ + cadence.String(hex.EncodeToString(signatureAlice)), + cadence.String(hex.EncodeToString(signatureBob)), + }) + + domainSeparationTag := cadence.String("FLOW-V0.0-user") + + arguments := []cadence.Value{ + publicKeys, + weights, + domainSeparationTag, + signatures, + toAddress, + fromAddress, + amount, + } + + encodedArguments := make([][]byte, 0, len(arguments)) + for _, argument := range arguments { + encodedArguments = append(encodedArguments, jsoncdc.MustEncode(argument)) + } + + result := executeScriptAndCheck(t, b, script, encodedArguments) + + assert.Equal(t, + cadence.NewBool(true), + result, + ) +} From 79fdc6c8ba530234e9784cedf91f7df962f9e0da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Thu, 17 Oct 2024 15:04:55 -0700 Subject: [PATCH 2/6] add Crypto contract to templates code --- lib/go/templates/templates.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/go/templates/templates.go b/lib/go/templates/templates.go index 208f7bcf5..906f63305 100644 --- a/lib/go/templates/templates.go +++ b/lib/go/templates/templates.go @@ -19,6 +19,7 @@ const ( placeholderFungibleTokenMVAddress = "\"FungibleTokenMetadataViews\"" placeholderMetadataViewsAddress = "\"MetadataViews\"" placeholderBurnerAddress = "\"Burner\"" + placeholderCryptoAddress = "\"Crypto\"" placeholderFlowTokenAddress = "\"FlowToken\"" placeholderIDTableAddress = "\"FlowIDTableStaking\"" placeholderLockedTokensAddress = "\"LockedTokens\"" @@ -38,6 +39,7 @@ type Environment struct { Network string ViewResolverAddress string BurnerAddress string + CryptoAddress string FungibleTokenAddress string NonFungibleTokenAddress string MetadataViewsAddress string @@ -90,6 +92,12 @@ func ReplaceAddresses(code string, env Environment) string { withHexPrefix(env.BurnerAddress), ) + code = strings.ReplaceAll( + code, + placeholderCryptoAddress, + withHexPrefix(env.CryptoAddress), + ) + code = strings.ReplaceAll( code, placeholderViewResolverAddress, From 0b1b28434b48f8900793d9c156777c8eca968d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Thu, 17 Oct 2024 17:07:33 -0700 Subject: [PATCH 3/6] add tests --- contracts/Crypto_test.cdc | 342 ++++++++++++++++++++++++++++++++++++++ contracts/flow.json | 30 ++++ 2 files changed, 372 insertions(+) create mode 100644 contracts/Crypto_test.cdc create mode 100644 contracts/flow.json diff --git a/contracts/Crypto_test.cdc b/contracts/Crypto_test.cdc new file mode 100644 index 000000000..0cdd42e61 --- /dev/null +++ b/contracts/Crypto_test.cdc @@ -0,0 +1,342 @@ +import Test +import Crypto from "Crypto.cdc" + +access(all) +fun setup() { + let err = Test.deployContract( + name: "Crypto", + path: "Crypto.cdc", + arguments: [] + ) + + Test.expect(err, Test.beNil()) +} + +access(all) +fun testCryptoHash() { + let hash = Crypto.hash([1, 2, 3], algorithm: HashAlgorithm.SHA3_256) + Test.assertEqual(32, hash.length) +} + +access(all) +fun testCryptoHashWithTag() { + let hash = Crypto.hashWithTag( + [1, 2, 3], + tag: "v0.1.tag", + algorithm: HashAlgorithm.SHA3_256 + ) + Test.assertEqual(32, hash.length) +} + +access(all) +fun testAddKeyToKeyList() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 1.0 + ) + + Test.assert(keyList.get(keyIndex: 0) != nil) +} + +access(all) +fun testGetKeyFromList() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 1.0 + ) + + Test.assert(keyList.get(keyIndex: 0) != nil) + Test.assert(keyList.get(keyIndex: 2) == nil) +} + +access(all) +fun testRevokeKeyFromList() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + keyList.revoke(keyIndex: 0) + keyList.revoke(keyIndex: 2) + + Test.assert(keyList.get(keyIndex: 0)!.isRevoked) + Test.assert(keyList.get(keyIndex: 2) == nil) +} + +access(all) +fun testKeyListVerify() { + let keyList = Crypto.KeyList() + + let publicKeyA = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + + keyList.add( + publicKeyA, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let publicKeyB = PublicKey( + publicKey: + "df9609ee588dd4a6f7789df8d56f03f545d4516f0c99b200d73b9a3afafc14de5d21a4fc7a2a2015719dc95c9e756cfa44f2a445151aaf42479e7120d83df956".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + + keyList.add( + publicKeyB, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ), + Crypto.KeyListSignature( + keyIndex: 1, + signature: + "bbdc5591c3f937a730d4f6c0a6fde61a0a6ceaa531ccb367c3559335ab9734f4f2b9da8adbe371f1f7da913b5a3fdd96a871e04f078928ca89a83d841c72fadf".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(isValid) +} + +access(all) +fun testKeyListVerifyInsufficientWeights() { + let keyList = Crypto.KeyList() + + let publicKeyA = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + + keyList.add( + publicKeyA, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.4 + ) + + let publicKeyB = PublicKey( + publicKey: + "df9609ee588dd4a6f7789df8d56f03f545d4516f0c99b200d73b9a3afafc14de5d21a4fc7a2a2015719dc95c9e756cfa44f2a445151aaf42479e7120d83df956".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + + keyList.add( + publicKeyB, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ), + Crypto.KeyListSignature( + keyIndex: 1, + signature: + "bbdc5591c3f937a730d4f6c0a6fde61a0a6ceaa531ccb367c3559335ab9734f4f2b9da8adbe371f1f7da913b5a3fdd96a871e04f078928ca89a83d841c72fadf".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(!isValid) +} + +access(all) +fun testKeyListVerifyWithRevokedKey() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + keyList.revoke(keyIndex: 0) + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(!isValid) +} + +access(all) +fun testKeyListVerifyWithMissingSignature() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 1, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(!isValid) +} + +access(all) +fun testKeyListVerifyDuplicateSignature() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ), + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "8870a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(!isValid) +} + +access(all) +fun testKeyListVerifyInvalidSignature() { + let keyList = Crypto.KeyList() + + let publicKey = PublicKey( + publicKey: + "db04940e18ec414664ccfd31d5d2d4ece3985acb8cb17a2025b2f1673427267968e52e2bbf3599059649d4b2cce98fdb8a3048e68abf5abe3e710129e90696ca".decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ) + keyList.add( + publicKey, + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: 0.5 + ) + + let signatureSet = [ + Crypto.KeyListSignature( + keyIndex: 0, + signature: + "db70a8cbe6f44932ba59e0d15a706214cc4ad2538deb12c0cf718d86f32c47765462a92ce2da15d4a29eb4e2b6fa05d08c7db5d5b2a2cd8c2cb98ded73da31f6".decodeHex() + ) + ] + + // "foo", encoded as UTF-8, in hex representation + let signedData = "666f6f".decodeHex() + + let isValid = keyList.verify( + signatureSet: signatureSet, + signedData: signedData, + domainSeparationTag: "FLOW-V0.0-user" + ) + + Test.assert(!isValid) +} + diff --git a/contracts/flow.json b/contracts/flow.json new file mode 100644 index 000000000..7604b6c30 --- /dev/null +++ b/contracts/flow.json @@ -0,0 +1,30 @@ +{ + "networks": { + "emulator": "127.0.0.1:3569", + "testing": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "sandboxnet": "access.sandboxnet.nodes.onflow.org:9000", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "contracts": { + "Crypto": { + "source": "crypto.cdc", + "aliases": { + "testing": "0x0000000000000007" + } + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "Crypto" + ] + } + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "b775d6da04a0b4819903d89a25395bfd90eb8559182255690c7c141edaeae923" + } + } +} \ No newline at end of file From 0320e412595fae108d040efd1195d4928fb31da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Thu, 17 Oct 2024 17:09:23 -0700 Subject: [PATCH 4/6] add Cadence-based tests to CI --- .github/workflows/ci.yml | 8 +++++++- contracts/flow.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f55a7812..ed45a24b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,5 +18,11 @@ jobs: run: flow version - name: Update PATH run: echo "/root/.local/bin" >> $GITHUB_PATH - - name: Run tests + - name: Run Go-based tests run: export GOPATH=$HOME/go && make ci + + - name: Install Flow CLI + run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" + + - name: Run Cadence-based tests + run: cd contracts && flow test --cover --covercode="contracts" Crypto_test.cdc diff --git a/contracts/flow.json b/contracts/flow.json index 7604b6c30..c9a8389a7 100644 --- a/contracts/flow.json +++ b/contracts/flow.json @@ -8,7 +8,7 @@ }, "contracts": { "Crypto": { - "source": "crypto.cdc", + "source": "Crypto.cdc", "aliases": { "testing": "0x0000000000000007" } From 107455d3092ba49e923e2c93e199ab846dc15bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Thu, 17 Oct 2024 17:14:33 -0700 Subject: [PATCH 5/6] ignore JSON and Cadence test files --- lib/go/contracts/contracts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/go/contracts/contracts.go b/lib/go/contracts/contracts.go index c2b16c6f5..b3ac7593a 100644 --- a/lib/go/contracts/contracts.go +++ b/lib/go/contracts/contracts.go @@ -1,6 +1,6 @@ package contracts -//go:generate go run github.com/kevinburke/go-bindata/go-bindata -prefix ../../../contracts -o internal/assets/assets.go -pkg assets -nometadata -nomemcopy ../../../contracts/... +//go:generate go run github.com/kevinburke/go-bindata/go-bindata -ignore=.*_test\.cdc -ignore=.*.json -prefix ../../../contracts -o internal/assets/assets.go -pkg assets -nometadata -nomemcopy ../../../contracts/... import ( "fmt" From 8bb8f95eeedc5078d94a4092f2ef05b3be1f1938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= <bastian@turbolent.com> Date: Mon, 21 Oct 2024 09:20:49 -0700 Subject: [PATCH 6/6] move CLI configuration for Crypto contract into existing file --- .github/workflows/ci.yml | 2 +- contracts/flow.json | 30 ------------------------------ flow.json | 6 ++++++ 3 files changed, 7 insertions(+), 31 deletions(-) delete mode 100644 contracts/flow.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed45a24b0..5d49337a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,4 +25,4 @@ jobs: run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)" - name: Run Cadence-based tests - run: cd contracts && flow test --cover --covercode="contracts" Crypto_test.cdc + run: flow test --cover --covercode="contracts" contracts/Crypto_test.cdc diff --git a/contracts/flow.json b/contracts/flow.json deleted file mode 100644 index c9a8389a7..000000000 --- a/contracts/flow.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "networks": { - "emulator": "127.0.0.1:3569", - "testing": "127.0.0.1:3569", - "mainnet": "access.mainnet.nodes.onflow.org:9000", - "sandboxnet": "access.sandboxnet.nodes.onflow.org:9000", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "contracts": { - "Crypto": { - "source": "Crypto.cdc", - "aliases": { - "testing": "0x0000000000000007" - } - } - }, - "deployments": { - "emulator": { - "emulator-account": [ - "Crypto" - ] - } - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "b775d6da04a0b4819903d89a25395bfd90eb8559182255690c7c141edaeae923" - } - } -} \ No newline at end of file diff --git a/flow.json b/flow.json index dc598c0f7..f4bd4cf5c 100644 --- a/flow.json +++ b/flow.json @@ -116,6 +116,12 @@ "testnet": "0x7aad92e5a0715d21", "mainnet": "0x62430cf28c26d095" } + }, + "Crypto": { + "source": "./contracts/Crypto.cdc", + "aliases": { + "testing": "0x0000000000000007" + } } }, "networks": {