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

PKG -- [sdk] TS voucher #1803

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b4b3bcc
Nialexsan/add types (#1710)
nialexsan Aug 4, 2023
9fca84a
Implement typescript for several branches & adjust configuration (#1750)
jribbink Aug 15, 2023
81d4b40
Convert @onflow/types to TS (#1760)
jribbink Aug 25, 2023
0960cdc
PKG -- [util-actor] Enhance TS support (#1761)
jribbink Sep 1, 2023
9554e0b
PKG -- [types] Simplify generics for @onflow/types (#1772)
jribbink Sep 8, 2023
6b81c1f
PKG -- [config] Convert @onflow/config to TS (#1731)
jribbink Sep 12, 2023
60d4b35
Fix JSDoc type generation (#1780)
jribbink Sep 14, 2023
86ce9f7
restore changeset
nialexsan Nov 1, 2023
a381814
align packages
nialexsan Nov 1, 2023
ac4e161
pre typescript
nialexsan Nov 1, 2023
04bd73e
any type for config
nialexsan Nov 1, 2023
da3bc15
update package lock
nialexsan Nov 1, 2023
df3dbac
PKG -- [util-encode-key] eslint ts config
nialexsan Aug 9, 2023
e04367c
more ts
nialexsan Aug 15, 2023
869ab99
move interaction types
nialexsan Aug 25, 2023
d1d5e5e
fix types
nialexsan Aug 25, 2023
dbf6bf8
more types
nialexsan Aug 25, 2023
0d47959
authz types
nialexsan Aug 25, 2023
fbb91f7
fix export
nialexsan Aug 25, 2023
6c4ecba
fix imports
nialexsan Aug 25, 2023
ac04e6b
fixed tests
nialexsan Aug 25, 2023
8662e4a
fix paths
nialexsan Aug 26, 2023
9783a33
fix path
nialexsan Aug 26, 2023
e111013
fix some tests
nialexsan Aug 30, 2023
503aded
clean console log
nialexsan Sep 12, 2023
7c28b15
fix logic
nialexsan Sep 12, 2023
3ce27e2
addressing PR comments
nialexsan Sep 25, 2023
3aca2d3
fix build arguments
nialexsan Sep 25, 2023
79a34d5
shallow copy acct
nialexsan Sep 25, 2023
31b7122
convert encode
nialexsan Sep 13, 2023
1050275
fix references
nialexsan Sep 13, 2023
bd2a320
revert changes
nialexsan Sep 13, 2023
eb422ea
ts resolve signature
nialexsan Sep 13, 2023
9313eff
fix resolve accounts and interaction types
nialexsan Nov 2, 2023
b73afdf
fixed types
nialexsan Nov 2, 2023
ffe5d30
fix path to interactions
nialexsan Nov 2, 2023
fa3db8c
Merge remote-tracking branch 'origin/master' into nialexsan/ts-conver…
nialexsan Nov 7, 2023
1921cbf
revert changes to changelogs
nialexsan Nov 7, 2023
8980a9e
Merge branch 'nialexsan/ts-conversion-fixed-merge-conflicts' into nia…
nialexsan Nov 7, 2023
cb56493
fix types for resolve-accounts
nialexsan Nov 8, 2023
e0d0d5d
Merge branch 'master' into nialexsan/ts-conversion-fixed-merge-conflicts
nialexsan Nov 8, 2023
29e7af0
Merge branch 'nialexsan/ts-conversion-fixed-merge-conflicts' into nia…
nialexsan Nov 9, 2023
03600db
Merge remote-tracking branch 'origin/master' into nialexsan/ts-conver…
nialexsan Nov 16, 2023
103a4ff
address comments
nialexsan Nov 27, 2023
e50b20d
changeset
nialexsan Nov 27, 2023
1a7a8ae
Merge remote-tracking branch 'origin/nialexsan/ts-conversion-fixed-me…
nialexsan Nov 27, 2023
3d70a23
Merge remote-tracking branch 'origin/master' into nialexsan/ts-conver…
nialexsan Nov 27, 2023
452e6f7
more descriptive names
nialexsan Nov 27, 2023
d2fceef
PKG -- [sdk] rename interfaces
nialexsan Nov 29, 2023
ba1dd47
PKG -- [typedefs] fix merge conflict
nialexsan Nov 29, 2023
7fb02aa
PKG -- [typedefs] fix merge conflicts
nialexsan Nov 29, 2023
9a5d701
Merge remote-tracking branch 'origin/master' into nialexsan/ts-conver…
nialexsan Nov 29, 2023
cbd92f2
PKG -- [sdk] fix interface imports
nialexsan Nov 29, 2023
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
2 changes: 1 addition & 1 deletion packages/fcl/src/wallet-provider-spec/draft-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ An authorization service is expected to know the Account and the Key that will b
FCL will use the `method` provided to request an array of composite signature from authorization service (Wrapped in a `PollingResponse`).
The authorization service will be sent a `Signable`.
The service is expected to construct an encoded message to sign from `Signable.voucher`.
It then needs to hash the encoded message, and prepend a required [transaction domain tag](https://github.com/onflow/flow-js-sdk/blob/master/packages/sdk/src/encode/encode.js#L12-L13).
It then needs to hash the encoded message, and prepend a required [transaction domain tag](../../../sdk/src/encode/encode.ts#L12-L13).
Finally it signs the payload with the user/s keys, producing a signature.
This signature, as a HEX string, is sent back to FCL as part of the `CompositeSignature` which includes the user address and keyID in the data property of a `PollingResponse`.

Expand Down
2 changes: 1 addition & 1 deletion packages/fcl/src/wallet-provider-spec/draft-v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ An authorization service is expected to know the Account and the Key that will b
FCL will use the `method` provided to request an array of composite signature from authorization service (Wrapped in a `PollingResponse`).
The authorization service will be sent a `Signable`.
The service is expected to construct an encoded message to sign from `Signable.voucher`.
It then needs to hash the encoded message, and prepend a required [transaction domain tag](https://github.com/onflow/flow-js-sdk/blob/master/packages/sdk/src/encode/encode.js#L12-L13).
It then needs to hash the encoded message, and prepend a required [transaction domain tag](../../../sdk/src/encode/encode.ts#L12-L13).
Finally it signs the payload with the user/s keys, producing a signature.
This signature, as a HEX string, is sent back to FCL as part of the `CompositeSignature` which includes the user address and keyID in the data property of a `PollingResponse`.

Expand Down
2 changes: 1 addition & 1 deletion packages/rlp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Buffer} from "buffer"

export {Buffer}

type EncodeInput =
export type EncodeInput =
| Buffer
| string
| number
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ const response = await sdk.send(await sdk.build([
- [`sdk.getCollection`](./src/build/build-get-collection)
- [`sdk.getEvents`](./src/build/build-get-events.js)
- [`sdk.getEventsAtBlockHeightRange`](./src/build/build-get-events-at-block-height-range.js)
- [`sdk.getEventsAtBlockIds`](./src/build/build-get-events-at-block-ids)
- [`sdk.getEventsAtBlockIds`](./src/build/build-get-events-at-block-ids.js)
- [`sdk.getLatestBlock`](./src/build/build-get-latest-block.js)
- [`sdk.getTransactionStatus`](./src/build/build-get-transaction-status.js)
- [`sdk.getTransaction`](./src/build/build-get-transaction.js)
Expand All @@ -526,15 +526,15 @@ const response = await sdk.send(await sdk.build([
- [`sdk.validator`](./src/build/build-validator.js)

- [Resolvers](./src/resolve)
- [`sdk.resolveAccounts`](./src/resolve/resolve-accounts.js)
- [`sdk.resolveAccounts`](./src/resolve/resolve-accounts.ts)
- [`sdk.resolveArguments`](./src/resolve/resolve-arguments.js)
- [`sdk.resolveCadence`](./src/resolve/resolve-cadence.js)
- [`sdk.resolveFinalNormalization`](./src/resolve/resolve-final-normalization.js)
- [`sdk.resolveVoucherIntercept`](./src/resolve/resolve-voucher-intercept.js)
- [`sdk.resolveProposerSequenceNumber`](./src/resolve/resolve-proposer-sequence-number.js)
- [`sdk.resolveRefBlockId`](./src/resolve/resolve-ref-block-id.js)
- [`sdk.resolveSignatures`](./src/resolve/resolve-signatures.js)
- [`sdk.resolveSignatures`](./src/resolve/resolve-signatures.ts)
- [`sdk.resolveValidators`](./src/resolve/resolve-validators.js)

- [Other Utils](./src/)
- [`sdk.voucherToTxId`](./src/resolve/voucher.js)
- [`sdk.voucherToTxId`](./src/resolve/voucher.ts)
2 changes: 1 addition & 1 deletion packages/sdk/src/contract.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as root from "./sdk"
import * as decode from "./decode/decode.js"
import * as encode from "./encode/encode.js"
import * as encode from "./encode/encode"
import * as interaction from "./interaction/interaction"
import * as send from "./send/send.js"
import * as template from "@onflow/util-template"
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/encode/encode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
encodeTransactionPayload,
encodeTransactionEnvelope,
encodeTxIdFromVoucher,
} from "./encode.js"
import * as root from "./encode.js"
} from "./encode"
import * as root from "./encode"

it("export contract interface", () => {
expect(root).toStrictEqual(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,73 @@
import {SHA3} from "sha3"
import {encode, Buffer} from "@onflow/rlp"
import {encode, Buffer, EncodeInput} from "@onflow/rlp"
import {sansPrefix} from "@onflow/util-address"

export const encodeTransactionPayload = tx =>
export const encodeTransactionPayload = (tx: Transaction) =>
prependTransactionDomainTag(rlpEncode(preparePayload(tx)))
export const encodeTransactionEnvelope = tx =>
export const encodeTransactionEnvelope = (tx: Transaction) =>
prependTransactionDomainTag(rlpEncode(prepareEnvelope(tx)))
export const encodeTxIdFromVoucher = voucher =>
export const encodeTxIdFromVoucher = (voucher: Voucher) =>
sha3_256(rlpEncode(prepareVoucher(voucher)))

const rightPaddedHexBuffer = (value, pad) =>
Buffer.from(value.padEnd(pad * 2, 0), "hex")
const rightPaddedHexBuffer = (value: string, pad: number) =>
Buffer.from(value.padEnd(pad * 2, "0"), "hex")

const leftPaddedHexBuffer = (value, pad) =>
Buffer.from(value.padStart(pad * 2, 0), "hex")
const leftPaddedHexBuffer = (value: string, pad: number) =>
Buffer.from(value.padStart(pad * 2, "0"), "hex")

const TRANSACTION_DOMAIN_TAG = rightPaddedHexBuffer(
Buffer.from("FLOW-V0.0-transaction").toString("hex"),
32
).toString("hex")
const prependTransactionDomainTag = tx => TRANSACTION_DOMAIN_TAG + tx
const prependTransactionDomainTag = (tx: string) => TRANSACTION_DOMAIN_TAG + tx

const addressBuffer = addr => leftPaddedHexBuffer(addr, 8)
const addressBuffer = (addr: string) => leftPaddedHexBuffer(addr, 8)

const blockBuffer = block => leftPaddedHexBuffer(block, 32)
const blockBuffer = (block: string) => leftPaddedHexBuffer(block, 32)

const argumentToString = arg => Buffer.from(JSON.stringify(arg), "utf8")
const argumentToString = (arg: Record<string, any>) => Buffer.from(JSON.stringify(arg), "utf8")

const scriptBuffer = script => Buffer.from(script, "utf8")
const signatureBuffer = signature => Buffer.from(signature, "hex")
const scriptBuffer = (script: string) => Buffer.from(script, "utf8")
const signatureBuffer = (signature: string) => Buffer.from(signature, "hex")

const rlpEncode = v => {
const rlpEncode = (v: EncodeInput) => {
return encode(v).toString("hex")
}

const sha3_256 = msg => {
const sha3_256 = (msg: string) => {
const sha = new SHA3(256)
sha.update(Buffer.from(msg, "hex"))
return sha.digest().toString("hex")
}

const preparePayload = tx => {
const preparePayload = (tx: Transaction) => {
validatePayload(tx)

return [
scriptBuffer(tx.cadence),
scriptBuffer(tx.cadence || ''),
tx.arguments.map(argumentToString),
blockBuffer(tx.refBlock),
blockBuffer(tx.refBlock || ''),
tx.computeLimit,
addressBuffer(sansPrefix(tx.proposalKey.address)),
addressBuffer(sansPrefix(tx.proposalKey.address || '')),
tx.proposalKey.keyId,
tx.proposalKey.sequenceNum,
addressBuffer(sansPrefix(tx.payer)),
tx.authorizers.map(authorizer => addressBuffer(sansPrefix(authorizer))),
]
}

const prepareEnvelope = tx => {
const prepareEnvelope = (tx: Transaction) => {
validateEnvelope(tx)

return [preparePayload(tx), preparePayloadSignatures(tx)]
}

const preparePayloadSignatures = tx => {
const preparePayloadSignatures = (tx: Transaction) => {
const signers = collectSigners(tx)

return tx.payloadSigs
.map(sig => {
return tx.payloadSigs?.map((sig: Sig) => {
return {
signerIndex: signers.get(sig.address),
signerIndex: signers.get(sig.address) || '',
keyId: sig.keyId,
sig: sig.sig,
}
Expand All @@ -79,45 +78,51 @@ const preparePayloadSignatures = tx => {

if (a.keyId > b.keyId) return 1
if (a.keyId < b.keyId) return -1

return 0
})
.map(sig => {
return [sig.signerIndex, sig.keyId, signatureBuffer(sig.sig)]
})
}

const collectSigners = tx => {
const signers = new Map()
const collectSigners = (tx: Voucher | Transaction) => {
const signers = new Map<string, number>()
let i = 0

const addSigner = addr => {
const addSigner = (addr: string) => {
if (!signers.has(addr)) {
signers.set(addr, i)
i++
}
}

addSigner(tx.proposalKey.address)
if (tx.proposalKey.address){
addSigner(tx.proposalKey.address)
}
addSigner(tx.payer)
tx.authorizers.forEach(addSigner)

return signers
}

const prepareVoucher = voucher => {
const prepareVoucher = (voucher: Voucher) => {
validateVoucher(voucher)

const signers = collectSigners(voucher)

const prepareSigs = sigs => {
const prepareSigs = (sigs: Sig[]) => {
return sigs
.map(({address, keyId, sig}) => {
return {signerIndex: signers.get(address), keyId, sig}
.map(({ address, keyId, sig }) => {
return { signerIndex: signers.get(address) || '', keyId, sig }
})
.sort((a, b) => {
if (a.signerIndex > b.signerIndex) return 1
if (a.signerIndex < b.signerIndex) return -1
if (a.keyId > b.keyId) return 1
if (a.keyId < b.keyId) return -1

return 0
})
.map(sig => {
return [sig.signerIndex, sig.keyId, signatureBuffer(sig.sig)]
Expand All @@ -143,23 +148,23 @@ const prepareVoucher = voucher => {
]
}

const validatePayload = tx => {
const validatePayload = (tx: Transaction) => {
payloadFields.forEach(field => checkField(tx, field))
proposalKeyFields.forEach(field =>
checkField(tx.proposalKey, field, "proposalKey")
)
}

const validateEnvelope = tx => {
const validateEnvelope = (tx: Transaction) => {
payloadSigsFields.forEach(field => checkField(tx, field))
tx.payloadSigs.forEach((sig, index) => {
tx.payloadSigs?.forEach((sig, index) => {
payloadSigFields.forEach(field =>
checkField(sig, field, "payloadSigs", index)
)
})
}

const validateVoucher = voucher => {
const validateVoucher = (voucher: Voucher) => {
payloadFields.forEach(field => checkField(voucher, field))
proposalKeyFields.forEach(field =>
checkField(voucher.proposalKey, field, "proposalKey")
Expand All @@ -178,12 +183,64 @@ const validateVoucher = voucher => {
})
}

const isNumber = v => typeof v === "number"
const isString = v => typeof v === "string"
const isObject = v => v !== null && typeof v === "object"
const isArray = v => isObject(v) && v instanceof Array
const isNumber = (v: any): v is number => typeof v === "number"
const isString = (v: any): v is string => typeof v === "string"
const isObject = (v: any) => v !== null && typeof v === "object"
const isArray = (v: any) => isObject(v) && v instanceof Array

interface VoucherArgument {
type: string
value: string
}

interface VoucherProposalKey {
address: string
keyId: number | null
sequenceNum: number | null
}

interface Sig {
address: string,
keyId: number | string,
sig: string,
}

export interface TransactionProposalKey {
address?: string
keyId?: number | string
sequenceNum?: number
}
export interface Transaction {
cadence: string | null;
refBlock: string | null;
computeLimit: string | null;
arguments: VoucherArgument[]
proposalKey: TransactionProposalKey
payer: string
authorizers: string[]
payloadSigs?: Sig[]
envelopeSigs?: TransactionProposalKey[]
}

export interface Voucher {
cadence: string;
refBlock: string;
computeLimit: number;
arguments: VoucherArgument[]
proposalKey: VoucherProposalKey
payer: string
authorizers: string[]
payloadSigs: Sig[]
envelopeSigs: Sig[]
}

interface PayloadField {
name: string,
check: (v: any) => boolean,
defaultVal?: string
}

const payloadFields = [
const payloadFields: PayloadField[] = [
{name: "cadence", check: isString},
{name: "arguments", check: isArray},
{name: "refBlock", check: isString, defaultVal: "0"},
Expand All @@ -193,42 +250,47 @@ const payloadFields = [
{name: "authorizers", check: isArray},
]

const proposalKeyFields = [
const proposalKeyFields: PayloadField[] = [
{name: "address", check: isString},
{name: "keyId", check: isNumber},
{name: "sequenceNum", check: isNumber},
]

const payloadSigsFields = [{name: "payloadSigs", check: isArray}]
const payloadSigsFields: PayloadField[] = [{name: "payloadSigs", check: isArray}]

const payloadSigFields = [
const payloadSigFields: PayloadField[] = [
{name: "address", check: isString},
{name: "keyId", check: isNumber},
{name: "sig", check: isString},
]

const envelopeSigsFields = [{name: "envelopeSigs", check: isArray}]
const envelopeSigsFields: PayloadField[] = [{ name: "envelopeSigs", check: isArray }]

const envelopeSigFields = [
const envelopeSigFields: PayloadField[] = [
{name: "address", check: isString},
{name: "keyId", check: isNumber},
{name: "sig", check: isString},
]

const checkField = (obj, field, base, index) => {
const checkField = (
obj: Record<string, any>,
field: PayloadField,
base?: string,
index?: number
) => {
const {name, check, defaultVal} = field
if (obj[name] == null && defaultVal != null) obj[name] = defaultVal
if (obj[name] == null) throw missingFieldError(name, base, index)
if (!check(obj[name])) throw invalidFieldError(name, base, index)
}

const printFieldName = (field, base, index) => {
const printFieldName = (field: string, base?: string, index?: number) => {
if (!!base)
return index == null ? `${base}.${field}` : `${base}.${index}.${field}`
return field
}

const missingFieldError = (field, base, index) =>
const missingFieldError = (field: string, base?: string, index?: number) =>
new Error(`Missing field ${printFieldName(field, base, index)}`)
const invalidFieldError = (field, base, index) =>
const invalidFieldError = (field: string, base?: string, index?: number) =>
new Error(`Invalid field ${printFieldName(field, base, index)}`)
Loading
Loading