Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Fix custom signers (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
jribbink authored Aug 3, 2022
1 parent d0ff6be commit 57edf7d
Show file tree
Hide file tree
Showing 26 changed files with 700 additions and 188 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-planets-beam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/flow-js-testing": minor
---

Flow JS Testing now exports [`SignatureAlgorithm`](/docs/api.md#signaturealgorithm) and [`HashAlgorithm`](/docs/api.md#hashalgorithm) which are enums that may be used with [`createAccount`](/docs/accounts.md#createaccountname-keys) and [`sendTransaction`](/docs/send-transactions.md)
2 changes: 1 addition & 1 deletion .changeset/clean-oranges-hammer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@onflow/flow-js-testing": minor
---

Allow custom transaction signers to be provided as object with `addr`, `privateKey`, `keyId`, `hashAlgorithm` keys as an alternative to supplying merely the signer's account address and having Flow JS Testing determine the rest. This allows for more complex transaction authorizers. See [documentation for examples](/docs/send-transactions.md).
Allow custom transaction signers to be provided as object with `addr`, `privateKey`, `keyId`, `hashAlgorithm`, `signatureAlgorithm` keys as an alternative to supplying merely the signer's account address and having Flow JS Testing determine the rest. This allows for more complex transaction authorizers. See [documentation for examples](/docs/send-transactions.md).
5 changes: 5 additions & 0 deletions .changeset/kind-fishes-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/flow-js-testing": minor
---

Flow JS Testing now exports the [`pubFlowKey`](/docs/api.md#pubflowkeykeyobject) method which may be used to generate an RLP-encoded `Buffer` representing a public key corresponding to a particular private key.
1 change: 0 additions & 1 deletion .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
"@onflow/flow-js-testing": "0.2.3-alpha.7"
},
"changesets": [
"clean-oranges-hammer",
"giant-dots-trade",
"gold-cheetahs-attend",
"long-hairs-collect",
Expand Down
5 changes: 5 additions & 0 deletions .changeset/tasty-fans-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/flow-js-testing": minor
---

Flow JS Testing now exports the [`createAccount`](/docs/accounts.md#createaccountname-keys) method which may be used to manually create an account with a given human-readable name & specified keys.
6 changes: 0 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
# flow-js-testing

## 0.3.0-alpha.13

### Minor Changes

- [#155](https://github.com/onflow/flow-js-testing/pull/155) [`9dcab53`](https://github.com/onflow/flow-js-testing/commit/9dcab535393654e3c6ba41a3ac41095519446c27) Thanks [@jribbink](https://github.com/jribbink)! - Allow custom transaction signers to be provided as object with `addr`, `privateKey`, `keyId`, `hashAlgorithm` keys as an alternative to supplying merely the signer's account address and having Flow JS Testing determine the rest. This allows for more complex transaction authorizers. See [documentation for examples](/docs/send-transactions.md).

## 0.3.0-alpha.12

### Patch Changes
Expand Down
24 changes: 14 additions & 10 deletions cadence/transactions/create-account.cdc
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import FlowManager from 0x01

transaction (_ name: String, pubKey: String, manager: Address) {
transaction (_ name: String?, pubKey: [String], manager: Address) {
prepare( admin: AuthAccount) {
let newAccount = AuthAccount(payer:admin)
newAccount.addPublicKey(pubKey.decodeHex())
for key in pubKey {
newAccount.addPublicKey(key.decodeHex())
}

let linkPath = FlowManager.accountManagerPath
let accountManager = getAccount(manager)
.getCapability(linkPath)!
.borrow<&FlowManager.Mapper>()!

// Create a record in account database
let address = newAccount.address
accountManager.setAddress(name, address: address)
if name != nil {
let linkPath = FlowManager.accountManagerPath
let accountManager = getAccount(manager)
.getCapability(linkPath)!
.borrow<&FlowManager.Mapper>()!

// Create a record in account database
let address = newAccount.address
accountManager.setAddress(name!, address: address)
}
}
}
146 changes: 146 additions & 0 deletions dev-test/account.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import path from "path"
import {
emulator,
init,
getAccountAddress,
executeScript,
createAccount,
HashAlgorithm,
SignatureAlgorithm,
} from "../src"
import {playgroundImport} from "../src/transformers"
import * as fcl from "@onflow/fcl"
import {validateKeyPair} from "./util/validate-key-pair"
import {permute} from "./util/permute"
import {isAddress} from "../src"

beforeEach(async () => {
const basePath = path.resolve(__dirname, "./cadence")
await init(basePath)
return emulator.start()
})

afterEach(async () => {
emulator.stop()
})

it("createAccount - should work with name and resolves to correct getAccountAddress", async () => {
const addr1 = await createAccount({
name: "Billy",
})

const addr2 = await getAccountAddress("Billy")

expect(addr1).toBe(addr2)
})

it("createAccount - should work without name and returns address", async () => {
const Billy = await createAccount({
name: "Billy",
})

expect(isAddress(Billy)).toBe(true)
})

test.each(permute(Object.keys(HashAlgorithm), Object.keys(SignatureAlgorithm)))(
"createAccount - should work with custom keys - hash algorithm %s - signing algorithm %s",
async (hashAlgorithm, signatureAlgorithm) => {
const privateKey = "1234"
const Billy = await createAccount({
name: "Billy",
keys: [
{
privateKey,
hashAlgorithm: HashAlgorithm[hashAlgorithm],
signatureAlgorithm: SignatureAlgorithm[signatureAlgorithm],
},
],
})

expect(isAddress(Billy)).toBe(true)

const keys = await fcl.account(Billy).then(d => d.keys)

expect(keys.length).toBe(1)
expect(keys[0].hashAlgoString).toBe(hashAlgorithm)
expect(keys[0].signAlgoString).toBe(signatureAlgorithm)
expect(keys[0].weight).toBe(1000)
expect(
validateKeyPair(keys[0].publicKey, privateKey, signatureAlgorithm)
).toBe(true)
}
)

test("createAccount - should work with custom keys - defaults to SHA3_256, ECDSA_P256", async () => {
const privateKey = "1234"
const Billy = await createAccount({
name: "Billy",
keys: [
{
privateKey,
},
],
})

expect(isAddress(Billy)).toBe(true)

const keys = await fcl.account(Billy).then(d => d.keys)

expect(keys.length).toBe(1)
expect(keys[0].hashAlgoString).toBe("SHA3_256")
expect(keys[0].signAlgoString).toBe("ECDSA_P256")
expect(keys[0].weight).toBe(1000)
expect(
validateKeyPair(
keys[0].publicKey,
privateKey,
SignatureAlgorithm.ECDSA_P256
)
).toBe(true)
})

it("createAccount - should add universal private key to account by default", async () => {
const Billy = await createAccount({
name: "Billy",
})

expect(isAddress(Billy)).toBe(true)
})

it("getAccountAddress - should return proper playground addresses", async () => {
const accounts = ["Alice", "Bob", "Charlie", "Dave", "Eve"]
for (const i in accounts) {
await getAccountAddress(accounts[i])
}

const code = `
pub fun main(address:Address):Address{
return getAccount(address).address
}
`

const playgroundAddresses = ["0x01", "0x02", "0x03", "0x04", "0x05"]
for (const i in playgroundAddresses) {
const [result] = await executeScript({
code,
transformers: [playgroundImport(accounts)],
args: [playgroundAddresses[i]],
})
const account = await getAccountAddress(accounts[i])
expect(result).toBe(account)
}
})

it("getAccountAddress - should create an account if does not exist", async () => {
const Billy = await getAccountAddress("Billy")

expect(isAddress(Billy)).toBe(true)
})

it("getAccountAddress - should resolve an already created account", async () => {
const Billy1 = await getAccountAddress("Billy")
const Billy2 = await getAccountAddress("Billy")

expect(isAddress(Billy1)).toBe(true)
expect(Billy1).toMatch(Billy2)
})
79 changes: 79 additions & 0 deletions dev-test/interaction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
shallThrow,
shallPass,
} from "../src"
import {createAccount} from "../src/account"
import {HashAlgorithm, pubFlowKey, SignatureAlgorithm} from "../src/crypto"
import {permute} from "./util/permute"

// We need to set timeout for a higher number, cause some transactions might take up some time
jest.setTimeout(10000)
Expand Down Expand Up @@ -116,6 +119,82 @@ describe("interactions - sendTransaction", () => {
await shallPass(sendTransaction({code, signers}))
})

test.each(
permute(Object.keys(HashAlgorithm), Object.keys(SignatureAlgorithm))
)(
"sendTransaction - shall pass with custom signer - %s hash algorithm, %s signature algorithm",
async (hashAlgorithm, signatureAlgorithm) => {
const privateKey = "1234"
const Adam = await createAccount({
name: "Adam",
keys: [
await pubFlowKey({
privateKey,
hashAlgorithm,
signatureAlgorithm,
weight: 1000,
}),
],
})

const code = `
transaction{
prepare(signer: AuthAccount){
assert(signer.address == ${Adam}, message: "Signer address must be equal to Adam's Address")
}
}
`
const signers = [
{
addr: Adam,
keyId: 0,
privateKey,
hashAlgorithm,
signatureAlgorithm,
},
]

await shallPass(sendTransaction({code, signers}))
}
)

test("sendTransaction - shall pass with custom signer - hashAlgorithm, signatureAlgorithm resolved via string - pubKey resolved via privKey", async () => {
const hashAlgorithm = "ShA3_256" //varying caps to test case insensitivity
const signatureAlgorithm = "eCdSA_P256"

const privateKey = "1234"
const Adam = await createAccount({
name: "Adam",
keys: [
{
privateKey,
hashAlgorithm,
signatureAlgorithm,
weight: 1000,
},
],
})

const code = `
transaction{
prepare(signer: AuthAccount){
assert(signer.address == ${Adam}, message: "Signer address must be equal to Adam's Address")
}
}
`
const signers = [
{
addr: Adam,
keyId: 0,
privateKey,
hashAlgorithm,
signatureAlgorithm,
},
]

await shallPass(sendTransaction({code, signers}))
})

test("sendTransaction - argument mapper - basic", async () => {
await shallPass(async () => {
const code = `
Expand Down
8 changes: 8 additions & 0 deletions dev-test/util/permute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const permute = (...values) =>
values.length > 1
? permute(
values[0].reduce((acc, i) => {
return [...acc, ...values[1].map(j => [].concat(i).concat(j))]
}, [])
)
: values[0]
33 changes: 33 additions & 0 deletions dev-test/util/validate-key-pair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {ec as EC} from "elliptic"
import {
resolveSignAlgoKey,
SignAlgoECMap,
SignatureAlgorithm,
} from "../../src/crypto"
import {isString} from "../../src/utils"

export function validateKeyPair(
publicKey,
privateKey,
signatureAlgorithm = SignatureAlgorithm.P256
) {
const signAlgoKey = resolveSignAlgoKey(signatureAlgorithm)
const curve = SignAlgoECMap[signAlgoKey]

const prepareKey = key => {
if (isString(key)) key = Buffer.from(key, "hex")
if (key.at(0) !== 0x04) key = Buffer.concat([Buffer.from([0x04]), key])
return key
}

publicKey = prepareKey(publicKey)
privateKey = prepareKey(privateKey)

const ec = new EC(curve)
const pair = ec.keyPair({
pub: publicKey,
priv: privateKey,
})

return pair.validate()?.result ?? false
}
Loading

0 comments on commit 57edf7d

Please sign in to comment.