wormhole/algorand/audit_test/src/wormhole/WormholeEncoders.ts

162 lines
4.6 KiB
TypeScript

import { decodeAddress, encodeUint64 } from 'algosdk'
import elliptic from 'elliptic'
import assert from 'assert'
import web3Utils from 'web3-utils'
import { zeroPad } from 'ethers/lib/utils'
import { concatArrays, encodeUint32, encodeUint16, encodeUint8, decodeBase16 } from '../sdk/Encoding'
import { GovenanceMessageType, GovernancePayload, RegisterChainPayload, SignedVAA, UnsignedVAA, VAAPayload, VAAPayloadType } from './WormholeTypes'
export const EMITTER_GOVERNANCE = decodeBase16('0000000000000000000000000000000000000000000000000000000000000004')
function encodeGovernancePayload(payload: GovernancePayload): Uint8Array {
let body: Uint8Array
switch (payload.type) {
case GovenanceMessageType.SetUpdateHash: {
body = payload.updateHash
break
}
case GovenanceMessageType.UpdateGuardians: {
body = concatArrays([
encodeUint32(payload.newGSIndex),
encodeUint8(payload.guardians.length),
...payload.guardians
])
break
}
case GovenanceMessageType.SetMessageFee: {
body = concatArrays([
zeroPad([], 24),
encodeUint64(payload.messageFee),
])
break
}
case GovenanceMessageType.SendAlgo: {
body = concatArrays([
zeroPad([], 24),
encodeUint64(payload.fee),
decodeAddress(payload.dest).publicKey,
])
break
}
default: {
throw new Error(`Unknown governance message type`)
}
}
const result = concatArrays([
decodeBase16('00000000000000000000000000000000000000000000000000000000436f7265'),
encodeUint8(payload.type),
encodeUint16(payload.targetChainId),
body,
])
return result
}
function encodeRegisterChainPayload(payload: RegisterChainPayload): Uint8Array {
return concatArrays([
decodeBase16('0000000000000000000000000000000000000000000000000000000000000000'),
decodeBase16('000000000000000000000000000000000000000000546f6b656e427269646765'), // "TokenBridge"
encodeUint8(1), // FIXME: Is this a version number? We should make it variable so we can test the contract
encodeUint16(payload.targetChainId),
encodeUint16(payload.emitterChainId),
payload.emitterAddress,
])
}
function encodePayload(payload: VAAPayload): Uint8Array {
switch (payload.type) {
case VAAPayloadType.Raw:
return payload.payload
case VAAPayloadType.Governance:
return encodeGovernancePayload(payload.payload)
case VAAPayloadType.RegisterChain:
return encodeRegisterChainPayload(payload.payload)
}
}
// NOTE: Ideally, this should not be exported so we can contain the complexity here
export function generateKeySet(signers: elliptic.ec.KeyPair[]): Uint8Array[] {
const slices = signers.map((signer) => {
const pub = signer.getPublic()
const x = pub.getX().toBuffer()
const y = pub.getY().toBuffer()
const combined = concatArrays([x, y])
const hashStr = web3Utils.keccak256('0x' + Buffer.from(combined).toString('hex'))
const hash = new Uint8Array(Buffer.from(hashStr.slice(2), 'hex'))
const result = hash.slice(12, 32)
assert(result.length === 20, `Expected 20 bytes for key, got ${result.length}`)
return result
})
return slices
}
export function signWormholeMessage(vaa: UnsignedVAA): SignedVAA {
assert(vaa.entries.length < 2 ** 8)
// Generate body of payload
const body = concatArrays([
encodeUint32(vaa.header.timestamp),
encodeUint32(vaa.header.nonce),
encodeUint16(vaa.header.chainId),
vaa.header.emitter,
encodeUint64(vaa.header.sequence),
encodeUint8(vaa.header.consistencyLevel),
encodePayload(vaa.payload),
])
// Generate hash of body
const hexHash = web3Utils.keccak256(web3Utils.keccak256('0x' + Buffer.from(body).toString('hex')))
const hash = new Uint8Array(Buffer.from(hexHash.slice(2), 'hex'))
// Generate signature set
const signatures = vaa.entries.map((entry, i) => {
// Create signature over hash
const signature: elliptic.ec.Signature = entry.signer.sign(hash, { canonical: true })
// Create resulting data structure
const result = concatArrays([
encodeUint8(i),
zeroPad(signature.r.toBuffer(), 32),
zeroPad(signature.s.toBuffer(), 32),
encodeUint8(signature.recoveryParam ?? 0),
])
// Validate result
assert(result.length === 66)
return result
})
// Generate key set data
const keys = generateKeySet(vaa.entries.map(({ signer }) => signer))
// Generate data needed to send VAA
return {
command: vaa.command,
gsIndex: vaa.gsIndex,
signatures,
keys,
hash,
sequence: vaa.header.sequence,
chainId: vaa.header.chainId,
emitter: vaa.header.emitter,
extraTmplSigs: vaa.extraTmplSigs,
data: concatArrays([
// Header
encodeUint8(vaa.version),
encodeUint32(vaa.gsIndex),
encodeUint8(vaa.entries.length),
// Signatures
...signatures,
body
])
}
}