162 lines
4.6 KiB
TypeScript
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
|
|
])
|
|
}
|
|
}
|