Skip to content
Closed
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
14 changes: 10 additions & 4 deletions packages/wallet/core/src/state/local/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ export class Provider implements ProviderInterface {
}),
])

let totalWeight = 0n
const encoded = Signature.fillLeaves(fromConfig.topology, (leaf) => {
if (Config.isSapientSignerLeaf(leaf)) {
const sapientSignature = signaturesOfSigners.find(
Expand All @@ -295,7 +294,6 @@ export class Provider implements ProviderInterface {
)?.signature

if (sapientSignature) {
totalWeight += leaf.weight
return sapientSignature
}
}
Expand All @@ -305,14 +303,22 @@ export class Provider implements ProviderInterface {
return undefined
}

totalWeight += leaf.weight
return signature
})

const topologyContext: Config.TopologyWeightContext = {
wallet,
chainId: candidate.payload.chainId,
payload: candidate.payload.content,
}
const { weight: totalWeight } = Config.getWeight(encoded, () => false, topologyContext)

if (totalWeight < fromConfig.threshold) {
continue
}

const minimalTopology = Config.minimiseSignedTopology(encoded, fromConfig.threshold, topologyContext)

best = {
nextImageHash: candidate.nextImageHash,
checkpoint: candidate.config!.checkpoint,
Expand All @@ -321,7 +327,7 @@ export class Provider implements ProviderInterface {
configuration: {
threshold: fromConfig.threshold,
checkpoint: fromConfig.checkpoint,
topology: encoded,
topology: minimalTopology,
},
},
}
Expand Down
10 changes: 8 additions & 2 deletions packages/wallet/core/src/state/sequence/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,19 @@ export class Provider implements ProviderInterface {
Hex.assert(toImageHash)
Hex.assert(signature)

const payload = Payload.fromConfigUpdate(toImageHash)
const decoded = Signature.decodeSignature(Hex.toBytes(signature))

const { configuration } = await Signature.recover(decoded, wallet, 0, Payload.fromConfigUpdate(toImageHash), {
const { configuration } = await Signature.recover(decoded, wallet, 0, payload, {
provider: passkeySignatureValidator,
})
const topology = Config.minimiseSignedTopology(configuration.topology, configuration.threshold, {
wallet,
chainId: 0,
payload,
})

return { imageHash: toImageHash, signature: { ...decoded, configuration } }
return { imageHash: toImageHash, signature: { ...decoded, configuration: { ...configuration, topology } } }
}),
)
}
Expand Down
147 changes: 143 additions & 4 deletions packages/wallet/primitives/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
SignatureOfSapientSignerLeaf,
SignatureOfSignerLeaf,
} from './signature.js'
import { hash as hashPayload } from './payload.js'
import type { Parented } from './payload.js'
import { Constants } from './index.js'

export type SignerLeaf = {
Expand Down Expand Up @@ -104,6 +106,19 @@ export type Config = {
checkpointer?: Address.Address
}

export type TopologyWeightContext = {
wallet: Address.Address
chainId: number
payload: Parented
}

export const MATCHING_SUBDIGEST_WEIGHT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn

type ResolvedTopologyWeightContext = {
digest: Bytes.Bytes
anyAddressDigest: Bytes.Bytes
}

export function isSignerLeaf(cand: unknown): cand is SignerLeaf {
return typeof cand === 'object' && cand !== null && 'type' in cand && cand.type === 'signer'
}
Expand Down Expand Up @@ -209,6 +224,41 @@ export function findSignerLeaf(
export function getWeight(
topology: RawTopology | RawConfig | Config,
canSign: (signer: SignerLeaf | SapientSignerLeaf) => boolean,
context?: TopologyWeightContext,
): { weight: bigint; maxWeight: bigint } {
return getWeightWithContext(topology, canSign, resolveTopologyWeightContext(context))
}

function resolveTopologyWeightContext(context?: TopologyWeightContext): ResolvedTopologyWeightContext | undefined {
if (!context) {
return undefined
}

return {
digest: hashPayload(context.wallet, context.chainId, context.payload),
anyAddressDigest: hashPayload(Constants.ZeroAddress, context.chainId, context.payload),
}
}

function getSubdigestWeight(
topology: SubdigestLeaf | AnyAddressSubdigestLeaf,
context?: ResolvedTopologyWeightContext,
): bigint {
if (!context) {
return 0n
}

if (isSubdigestLeaf(topology)) {
return Bytes.isEqual(Bytes.fromHex(topology.digest), context.digest) ? MATCHING_SUBDIGEST_WEIGHT : 0n
}

return Bytes.isEqual(Bytes.fromHex(topology.digest), context.anyAddressDigest) ? MATCHING_SUBDIGEST_WEIGHT : 0n
}

function getWeightWithContext(
topology: RawTopology | RawConfig | Config,
canSign: (signer: SignerLeaf | SapientSignerLeaf) => boolean,
context?: ResolvedTopologyWeightContext,
): { weight: bigint; maxWeight: bigint } {
topology = isRawConfig(topology) || isConfig(topology) ? topology.topology : topology

Expand All @@ -223,23 +273,112 @@ export function getWeight(
} else if (isSapientSignerLeaf(topology)) {
return { weight: 0n, maxWeight: canSign(topology) ? topology.weight : 0n }
} else if (isSubdigestLeaf(topology)) {
return { weight: 0n, maxWeight: 0n }
const weight = getSubdigestWeight(topology, context)
return { weight, maxWeight: weight }
} else if (isAnyAddressSubdigestLeaf(topology)) {
return { weight: 0n, maxWeight: 0n }
const weight = getSubdigestWeight(topology, context)
return { weight, maxWeight: weight }
} else if (isRawNestedLeaf(topology)) {
const { weight, maxWeight } = getWeight(topology.tree, canSign)
const { weight, maxWeight } = getWeightWithContext(topology.tree, canSign, context)
return {
weight: weight >= topology.threshold ? topology.weight : 0n,
maxWeight: maxWeight >= topology.threshold ? topology.weight : 0n,
}
} else if (isNodeLeaf(topology)) {
return { weight: 0n, maxWeight: 0n }
} else {
const [left, right] = [getWeight(topology[0], canSign), getWeight(topology[1], canSign)]
const [left, right] = [
getWeightWithContext(topology[0], canSign, context),
getWeightWithContext(topology[1], canSign, context),
]
return { weight: left.weight + right.weight, maxWeight: left.maxWeight + right.maxWeight }
}
}

type MinimisedTopologyPlan = {
weight: bigint
topology: Topology
}

function stripSignedState(leaf: SignerLeaf | SapientSignerLeaf): SignerLeaf | SapientSignerLeaf {
const { signed: _signed, signature: _signature, ...rest } = leaf
return rest
}

function buildMinimisedTopologyPlans(
topology: Topology,
context?: ResolvedTopologyWeightContext,
): Array<MinimisedTopologyPlan | undefined> {
if (isSignedSignerLeaf(topology) || isSignedSapientSignerLeaf(topology)) {
return [
{ weight: 0n, topology: stripSignedState(topology) },
{ weight: topology.weight, topology },
]
}

if (isSubdigestLeaf(topology) || isAnyAddressSubdigestLeaf(topology)) {
return [{ weight: getSubdigestWeight(topology, context), topology }]
}

if (isSignerLeaf(topology) || isSapientSignerLeaf(topology) || isNodeLeaf(topology)) {
return [{ weight: 0n, topology }]
}

if (isNestedLeaf(topology)) {
return buildMinimisedTopologyPlans(topology.tree, context).map((plan) => {
if (!plan) {
return undefined
}

return {
weight: plan.weight >= topology.threshold ? topology.weight : 0n,
topology: {
...topology,
tree: plan.topology,
},
}
})
}

if (isNode(topology)) {
const leftPlans = buildMinimisedTopologyPlans(topology[0], context)
const rightPlans = buildMinimisedTopologyPlans(topology[1], context)
const plans = new Array<MinimisedTopologyPlan | undefined>(leftPlans.length + rightPlans.length - 1)

for (let total = 0; total < plans.length; total++) {
const maxLeft = Math.min(total, leftPlans.length - 1)
const minLeft = Math.max(0, total - (rightPlans.length - 1))

// Iterate from right to left so earlier topology positions win ties.
for (let leftCount = maxLeft; leftCount >= minLeft; leftCount--) {
const leftPlan = leftPlans[leftCount]
const rightPlan = rightPlans[total - leftCount]

if (!leftPlan || !rightPlan) {
continue
}

const weight = leftPlan.weight + rightPlan.weight
if (!plans[total] || weight > plans[total]!.weight) {
plans[total] = {
weight,
topology: [leftPlan.topology, rightPlan.topology],
}
}
}
}

return plans
}

throw new Error('Invalid topology')
}

export function minimiseSignedTopology(topology: Topology, threshold: bigint, context?: TopologyWeightContext): Topology {
const plans = buildMinimisedTopologyPlans(topology, resolveTopologyWeightContext(context))
return plans.find((plan) => plan && plan.weight >= threshold)?.topology ?? topology
}

export function hashConfiguration(topology: Topology | Config): Bytes.Bytes {
if (isConfig(topology)) {
let root = hashConfiguration(topology.topology)
Expand Down
9 changes: 3 additions & 6 deletions packages/wallet/primitives/src/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
SignerLeaf,
SubdigestLeaf,
AnyAddressSubdigestLeaf,
MATCHING_SUBDIGEST_WEIGHT,
Topology,
hashConfiguration,
isNestedLeaf,
Expand Down Expand Up @@ -1312,17 +1313,13 @@ async function recoverTopology(
} else if (isSubdigestLeaf(topology)) {
return {
topology,
weight: Bytes.isEqual(Bytes.fromHex(topology.digest), digest)
? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
: 0n,
weight: Bytes.isEqual(Bytes.fromHex(topology.digest), digest) ? MATCHING_SUBDIGEST_WEIGHT : 0n,
}
} else if (isAnyAddressSubdigestLeaf(topology)) {
const anyAddressOpHash = hash(Constants.ZeroAddress, chainId, payload)
return {
topology,
weight: Bytes.isEqual(Bytes.fromHex(topology.digest), anyAddressOpHash)
? 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn
: 0n,
weight: Bytes.isEqual(Bytes.fromHex(topology.digest), anyAddressOpHash) ? MATCHING_SUBDIGEST_WEIGHT : 0n,
}
} else if (isNodeLeaf(topology)) {
return { topology, weight: 0n }
Expand Down
Loading
Loading