add support for XChainClaim

This commit is contained in:
Mayukha Vadari
2022-05-31 16:33:07 -04:00
parent 1b82ee19e2
commit fad3949fc4
10 changed files with 355 additions and 4 deletions

View File

@@ -21,7 +21,7 @@
"UInt32": 2, "UInt32": 2,
"STArray": 15, "STArray": 15,
"Sidechain": 24, "Sidechain": 24,
"XchainClaimProof": 25 "XChainClaimProof": 25
}, },
"LEDGER_ENTRY_TYPES": { "LEDGER_ENTRY_TYPES": {
"Any": -3, "Any": -3,

View File

@@ -147,7 +147,8 @@ class Amount extends SerializedType {
*/ */
toJSON(): AmountObject | string { toJSON(): AmountObject | string {
if (this.isNative()) { if (this.isNative()) {
const bytes = this.bytes const bytes = Buffer.alloc(this.bytes.length)
this.bytes.copy(bytes)
const isPositive = bytes[0] & 0x40 const isPositive = bytes[0] & 0x40
const sign = isPositive ? '' : '-' const sign = isPositive ? '' : '-'
bytes[0] &= 0x3f bytes[0] &= 0x3f

View File

@@ -14,6 +14,7 @@ import { Hash256 } from './hash-256'
import { IssuedCurrency } from './issued-currency' import { IssuedCurrency } from './issued-currency'
import { PathSet } from './path-set' import { PathSet } from './path-set'
import { Sidechain } from './sidechain' import { Sidechain } from './sidechain'
import { Signature } from './signature'
import { STArray } from './st-array' import { STArray } from './st-array'
import { STObject } from './st-object' import { STObject } from './st-object'
import { UInt16 } from './uint-16' import { UInt16 } from './uint-16'
@@ -21,6 +22,7 @@ import { UInt32 } from './uint-32'
import { UInt64 } from './uint-64' import { UInt64 } from './uint-64'
import { UInt8 } from './uint-8' import { UInt8 } from './uint-8'
import { Vector256 } from './vector-256' import { Vector256 } from './vector-256'
import { XChainClaimProof } from './xchain-claim-proof'
const coreTypes = { const coreTypes = {
AccountID, AccountID,
@@ -33,6 +35,7 @@ const coreTypes = {
IssuedCurrency, IssuedCurrency,
PathSet, PathSet,
Sidechain, Sidechain,
Signature,
STArray, STArray,
STObject, STObject,
UInt8, UInt8,
@@ -40,6 +43,7 @@ const coreTypes = {
UInt32, UInt32,
UInt64, UInt64,
Vector256, Vector256,
XChainClaimProof,
} }
Object.values(Field).forEach((field) => { Object.values(Field).forEach((field) => {

View File

@@ -0,0 +1,131 @@
import { BinaryParser } from '../serdes/binary-parser'
import { JsonObject, SerializedType } from './serialized-type'
import { Buffer } from 'buffer/'
import { Blob } from './blob'
import { decodeAccountPublic, encodeAccountPublic } from 'ripple-address-codec'
/**
* Interface for JSON objects that represent amounts
*/
interface SignatureObject extends JsonObject {
signature: string
signing_key: string
}
/**
* Type guard for AmountObject
*/
function isSignatureObject(arg): arg is SignatureObject {
const keys = Object.keys(arg).sort()
return (
keys.length === 2 && keys[0] === 'signature' && keys[1] === 'signing_key'
)
}
function encodeVariableLength(length: number): Buffer {
const lenBytes = Buffer.alloc(3)
if (length <= 192) {
lenBytes[0] = length
return lenBytes.slice(0, 1)
} else if (length <= 12480) {
length -= 193
lenBytes[0] = 193 + (length >>> 8)
lenBytes[1] = length & 0xff
return lenBytes.slice(0, 2)
} else if (length <= 918744) {
length -= 12481
lenBytes[0] = 241 + (length >>> 16)
lenBytes[1] = (length >> 8) & 0xff
lenBytes[2] = length & 0xff
return lenBytes.slice(0, 3)
}
throw new Error('Overflow error')
}
/**
* Class for serializing/Deserializing Amounts
*/
class Signature extends SerializedType {
static readonly ZERO_SIGNATURE: Signature = new Signature(
Buffer.concat([Buffer.alloc(1), Buffer.from([33]), Buffer.alloc(33)]),
)
constructor(bytes: Buffer) {
super(bytes ?? Signature.ZERO_SIGNATURE.bytes)
}
/**
* Construct an amount from an IOU or string amount
*
* @param value An Amount, object representing an IOU, or a string
* representing an integer amount
* @returns An Amount object
*/
static from<T extends Signature | SignatureObject>(value: T): Signature {
if (value instanceof Signature) {
return value
}
if (isSignatureObject(value)) {
const signature = Blob.from(value.signature).toBytes()
const signing_key = new Blob(
Buffer.from(decodeAccountPublic(value.signing_key)),
).toBytes()
return new Signature(
Buffer.concat([
encodeVariableLength(signature.length),
signature,
encodeVariableLength(signing_key.length),
signing_key,
]),
)
}
throw new Error('Invalid type to construct a Signature')
}
/**
* Read an amount from a BinaryParser
*
* @param parser BinaryParser to read the Amount from
* @returns An Amount object
*/
static fromParser(parser: BinaryParser): Signature {
const bytes: Array<Buffer> = []
const signatureLength = parser.readVariableLengthLength()
bytes.push(encodeVariableLength(signatureLength))
bytes.push(Blob.fromParser(parser, signatureLength).toBytes())
const signingKeyLength = parser.readVariableLengthLength()
bytes.push(encodeVariableLength(signingKeyLength))
bytes.push(Blob.fromParser(parser, signingKeyLength).toBytes())
return new Signature(Buffer.concat(bytes))
}
/**
* Get the JSON representation of this Amount
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): SignatureObject {
const parser = new BinaryParser(this.toString())
const signatureLength = parser.readVariableLengthLength()
const signature = Blob.fromParser(
parser,
signatureLength,
).toJSON() as string
const signingKeyLength = parser.readVariableLengthLength()
const signingKey = Blob.fromParser(parser, signingKeyLength)
return {
signature,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
signing_key: encodeAccountPublic(signingKey.toBytes()),
}
}
}
export { Signature, SignatureObject }

View File

@@ -6,7 +6,7 @@ import { Buffer } from 'buffer/'
* Derived UInt class for serializing/deserializing 32 bit UInt * Derived UInt class for serializing/deserializing 32 bit UInt
*/ */
class UInt32 extends UInt { class UInt32 extends UInt {
protected static readonly width: number = 32 / 8 // 4 public static readonly width: number = 32 / 8 // 4
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width)) static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width))
constructor(bytes: Buffer) { constructor(bytes: Buffer) {

View File

@@ -6,7 +6,7 @@ import { Buffer } from 'buffer/'
* Derived UInt class for serializing/deserializing 8 bit UInt * Derived UInt class for serializing/deserializing 8 bit UInt
*/ */
class UInt8 extends UInt { class UInt8 extends UInt {
protected static readonly width: number = 8 / 8 // 1 public static readonly width: number = 8 / 8 // 1
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width)) static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width))
constructor(bytes: Buffer) { constructor(bytes: Buffer) {

View File

@@ -0,0 +1,157 @@
import { BinaryParser } from '../serdes/binary-parser'
import { JsonObject, SerializedType } from './serialized-type'
import { Buffer } from 'buffer/'
import { Sidechain, SidechainObject } from './sidechain'
import { Signature, SignatureObject } from './signature'
import { Amount } from './amount'
import { UInt8 } from './uint-8'
import { UInt32 } from './uint-32'
/**
* Constants for separating Paths in a PathSet
*/
const ARRAY_END_BYTE = 0xf1
/**
* Interface for JSON objects that represent amounts
*/
interface XChainClaimProofObject extends JsonObject {
amount: string
sidechain: SidechainObject
signatures: SignatureObject[]
was_src_chain_send: boolean
xchain_seq: number
}
/**
* Type guard for AmountObject
*/
function isProofObject(arg): arg is XChainClaimProofObject {
const keys = Object.keys(arg).sort()
return (
keys.length === 5 &&
keys[0] === 'amount' &&
keys[1] === 'sidechain' &&
keys[2] === 'signatures' &&
keys[3] === 'was_src_chain_send' &&
keys[4] === 'xchain_seq'
)
}
/**
* Class for serializing/Deserializing Amounts
*/
class XChainClaimProof extends SerializedType {
static readonly ZERO_PROOF: XChainClaimProof = new XChainClaimProof(
Buffer.concat([
Amount.defaultAmount.toBytes(),
Sidechain.ZERO_SIDECHAIN.toBytes(),
Buffer.from([ARRAY_END_BYTE]),
UInt8.defaultUInt8.toBytes(),
UInt32.defaultUInt32.toBytes(),
]),
)
constructor(bytes: Buffer) {
super(bytes ?? XChainClaimProof.ZERO_PROOF.bytes)
}
/**
* Construct an amount from an IOU or string amount
*
* @param value An Amount, object representing an IOU, or a string
* representing an integer amount
* @returns An Amount object
*/
static from<T extends XChainClaimProof | XChainClaimProofObject>(
value: T,
): XChainClaimProof {
if (value instanceof XChainClaimProof) {
return value
}
if (isProofObject(value)) {
const amount = Amount.from(value.amount).toBytes()
const sidechain = Sidechain.from(value.sidechain).toBytes()
const signatures: Array<Buffer> = []
value.signatures.forEach((signature: SignatureObject) => {
signatures.push(Signature.from(signature).toBytes())
})
signatures.push(Buffer.from([ARRAY_END_BYTE]))
const was_src_chain_send = UInt8.from(
Number(value.was_src_chain_send),
).toBytes()
const xchain_seq = UInt32.from(value.xchain_seq).toBytes()
return new XChainClaimProof(
Buffer.concat([
amount,
sidechain,
...signatures,
was_src_chain_send,
xchain_seq,
]),
)
}
throw new Error('Invalid type to construct a XChainClaimProof')
}
/**
* Read an amount from a BinaryParser
*
* @param parser BinaryParser to read the Amount from
* @returns An Amount object
*/
static fromParser(parser: BinaryParser): XChainClaimProof {
const bytes: Array<Buffer> = []
bytes.push(Amount.fromParser(parser).toBytes())
bytes.push(Sidechain.fromParser(parser).toBytes())
while (!parser.end()) {
bytes.push(Signature.fromParser(parser).toBytes())
if (parser.peek() === ARRAY_END_BYTE) {
bytes.push(parser.read(1))
break
}
}
bytes.push(parser.read(UInt8.width))
bytes.push(parser.read(UInt32.width))
return new XChainClaimProof(Buffer.concat(bytes))
}
/**
* Get the JSON representation of this Amount
*
* @returns the JSON interpretation of this.bytes
*/
toJSON(): XChainClaimProofObject {
const parser = new BinaryParser(this.toString())
const amount = Amount.fromParser(parser).toJSON()
const sidechain = Sidechain.fromParser(parser).toJSON()
const signatures: SignatureObject[] = []
while (!parser.end()) {
if (parser.peek() === ARRAY_END_BYTE) {
parser.skip(1)
break
}
signatures.push(Signature.fromParser(parser).toJSON())
}
const was_src_chain_send = UInt8.fromParser(parser).toJSON()
const xchain_seq = UInt32.fromParser(parser).toJSON()
return {
amount: amount as string,
sidechain,
signatures,
was_src_chain_send: Boolean(was_src_chain_send),
xchain_seq: xchain_seq as number,
}
}
}
export { XChainClaimProof, XChainClaimProofObject }

View File

@@ -108,6 +108,7 @@ const NegativeUNL = require('./fixtures/negative-unl.json')
const XChainDoorCreate = require('./fixtures/xchain-door-create.json') const XChainDoorCreate = require('./fixtures/xchain-door-create.json')
const XChainSeqNumCreate = require('./fixtures/xchain-seqnum-create.json') const XChainSeqNumCreate = require('./fixtures/xchain-seqnum-create.json')
const XChainTransfer = require('./fixtures/xchain-transfer.json') const XChainTransfer = require('./fixtures/xchain-transfer.json')
const XChainClaim = require('./fixtures/xchain-claim.json')
function bytesListTest() { function bytesListTest() {
const list = new BytesList() const list = new BytesList()
@@ -251,6 +252,12 @@ function sidechainTest() {
test('can deserialize XChainTransfer', () => { test('can deserialize XChainTransfer', () => {
expect(decode(XChainTransfer.binary)).toEqual(XChainTransfer.tx) expect(decode(XChainTransfer.binary)).toEqual(XChainTransfer.tx)
}) })
test('can serialize XChainClaim', () => {
expect(encode(XChainClaim.tx)).toEqual(XChainClaim.binary)
})
test('can deserialize XChainClaim', () => {
expect(decode(XChainClaim.binary)).toEqual(XChainClaim.tx)
})
} }
function omitUndefinedTest() { function omitUndefinedTest() {

View File

@@ -0,0 +1,50 @@
{
"binary": "12002122800000008114621D345F8F094A085132431C69C89EC05D212CC28314CA64525733C3BEED910CFE2AE280D3C078DABB4B0119400000003B9ACA00CC86E58C9B58D4CF71CB8C1B41F21BB290CE13D40000000000000000000000000000000000000000C48CAD01682D7A86296EF14523074D4852C02EA900000000000000000000000000000000000000004730450221008CC9842A6855A37131FE7FB978675DCF329AC5CD7C881FAF6D13CDC23363059F02203A10475640C2C09541A55098109BB3326D3F49E2304710736A4E3C2773539B012103ADB44CA8E56F78A0096825E5667C450ABD5C24C34E027BC1AAF7E5BD114CB5B5473045022100B93B51FDBE42634828EEF7F2E5FA950557283648C5D39196ED1B08F8B394959102201F162F43B41D0D9316BF3B7AFA3BC9CA0EC1FCECF6EE8C94F58616BEA2D31F2C2102A14E886B3C3579FBAE3139F29728B903E6F4295AEE92160C8480695524D66A154630440220254038C5D6106246AEBB2A94E60CE79102321FDFD0468AA40C5E7F2DF1C205FE02204CEE5B3BFFDDCB67AE593348E8D8551E82770415BC9581EF0EF20DD000DD550C2102F7390DCF3352060847B81666EBAC79D52DEA2443BDF58439F75397C45334E2DC473045022100AA28592882A3B7C769B32564EDF9F816179D42D8C0E3988567F816E9A5453C6002202BA57DC085B9EB8427FBD58E05B3617183AC216EC7066F03FA4896D8B449490E2102498BD8CD9CA6A4BA567A2ECFA163F118AFD30511CBBA71429C2EC2F74D760592473045022100B9D649491723810B705282B66EAD6075235A1954BEEE054FF68066FADE11DA1C02205D977AE7C6706224A5A3AA75A53E13CA3DE9D67084405CD9BB474554EEE5C0702103219642288DEE8A3AA8FEA1F7DAE9ED4D9A9F0EADA1E2DE3DB56DD9598D9AD817F10100000001",
"tx": {
"Account" : "r9A8UyNpW3X46FUc6P7JZqgn6WgAPjBwPg",
"Destination" : "rKT9gDkaedAosiHyHZTjyZs2HvXpzuiGmC",
"Flags" : 2147483648,
"TransactionType" : "XChainClaim",
"XChainClaimProof" :
{
"amount" : "1000000000",
"sidechain" :
{
"dst_chain_door" : "rKeSSvHvaMZJp9ykaxutVwkhZgWuWMLnQt",
"dst_chain_issue" : "XRP",
"src_chain_door" : "rJvExveLEL4jNDEeLKCVdxaSCN9cEBnEQC",
"src_chain_issue" : "XRP"
},
"signatures" :
[
{
"signature" : "30450221008CC9842A6855A37131FE7FB978675DCF329AC5CD7C881FAF6D13CDC23363059F02203A10475640C2C09541A55098109BB3326D3F49E2304710736A4E3C2773539B01",
"signing_key" : "aBRDkDojKThV2dNbjvxQDwxtCLkgpd16bVFNCRhGMQKrG1VjGQmi"
},
{
"signature" : "3045022100B93B51FDBE42634828EEF7F2E5FA950557283648C5D39196ED1B08F8B394959102201F162F43B41D0D9316BF3B7AFA3BC9CA0EC1FCECF6EE8C94F58616BEA2D31F2C",
"signing_key" : "aBPBY4NyQAGwBHLEYchMNtsGPLmsCDeqXs3dvvTuZffYxgH6pb5Y"
},
{
"signature" : "30440220254038C5D6106246AEBB2A94E60CE79102321FDFD0468AA40C5E7F2DF1C205FE02204CEE5B3BFFDDCB67AE593348E8D8551E82770415BC9581EF0EF20DD000DD550C",
"signing_key" : "aBPq4y6k28jQNzcNMESzFPhwt1gdNTQCzBMD6iSAW8rEqvuX2fi6"
},
{
"signature" : "3045022100AA28592882A3B7C769B32564EDF9F816179D42D8C0E3988567F816E9A5453C6002202BA57DC085B9EB8427FBD58E05B3617183AC216EC7066F03FA4896D8B449490E",
"signing_key" : "aB4WteEVakaAcnKq6VDxVA812ehqgr25XpQnycM96RjHUrrh76MH"
},
{
"signature" : "3045022100B9D649491723810B705282B66EAD6075235A1954BEEE054FF68066FADE11DA1C02205D977AE7C6706224A5A3AA75A53E13CA3DE9D67084405CD9BB474554EEE5C070",
"signing_key" : "aBQwsfTh1zikjFDLpkihw6Ug3iyeeHP93P3rZ8pwTDJe7veiRMSH"
}
],
"was_src_chain_send" : true,
"xchain_seq" : 1
}
}
}

View File

@@ -21,6 +21,7 @@ describe('SerializedType interfaces', () => {
}) })
test(`${name}.from(json).toJSON() == json`, () => { test(`${name}.from(json).toJSON() == json`, () => {
const newJSON = new Value().toJSON() const newJSON = new Value().toJSON()
console.log(newJSON)
expect(Value.from(newJSON).toJSON()).toEqual(newJSON) expect(Value.from(newJSON).toJSON()).toEqual(newJSON)
}) })
describe(`${name} supports all methods of the SerializedType mixin`, () => { describe(`${name} supports all methods of the SerializedType mixin`, () => {