mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-29 08:35:49 +00:00
Add signWithKeypair (#769)
This commit is contained in:
@@ -3847,7 +3847,10 @@ return api.prepareCheckCash(address, checkCash).then(prepared =>
|
|||||||
|
|
||||||
## sign
|
## sign
|
||||||
|
|
||||||
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
|
```
|
||||||
|
sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}
|
||||||
|
sign(txJSON: string, keypair: Object, options: Object): {signedTransaction: string, id: string}
|
||||||
|
```
|
||||||
|
|
||||||
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||||
|
|
||||||
@@ -3856,9 +3859,12 @@ Sign a prepared transaction. The signed transaction must subsequently be [submit
|
|||||||
Name | Type | Description
|
Name | Type | Description
|
||||||
---- | ---- | -----------
|
---- | ---- | -----------
|
||||||
txJSON | string | Transaction represented as a JSON string in rippled format.
|
txJSON | string | Transaction represented as a JSON string in rippled format.
|
||||||
secret | secret string | The secret of the account that is initiating the transaction.
|
keypair | object | *Optional* The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).
|
||||||
|
*keypair.* privateKey | privateKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 private key.
|
||||||
|
*keypair.* publicKey | publicKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key.
|
||||||
options | object | *Optional* Options that control the type of signature that will be generated.
|
options | object | *Optional* Options that control the type of signature that will be generated.
|
||||||
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
|
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
|
||||||
|
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field is exclusive with keypair).
|
||||||
|
|
||||||
### Return Value
|
### Return Value
|
||||||
|
|
||||||
@@ -3874,7 +3880,8 @@ id | [id](#transaction-id) | The [Transaction ID](#transaction-id) of the signed
|
|||||||
```javascript
|
```javascript
|
||||||
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
|
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
|
||||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||||
return api.sign(txJSON, secret);
|
const keypair = { privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A', publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' };
|
||||||
|
return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
## sign
|
## sign
|
||||||
|
|
||||||
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
|
```
|
||||||
|
sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}
|
||||||
|
sign(txJSON: string, keypair: Object, options: Object): {signedTransaction: string, id: string}
|
||||||
|
```
|
||||||
|
|
||||||
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
|
||||||
|
|
||||||
@@ -19,7 +22,8 @@ This method returns an object with the following structure:
|
|||||||
```javascript
|
```javascript
|
||||||
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
|
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
|
||||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||||
return api.sign(txJSON, secret);
|
const keypair = { privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A', publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' };
|
||||||
|
return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||||
```
|
```
|
||||||
|
|
||||||
<%- renderFixture("responses/sign.json") %>
|
<%- renderFixture("responses/sign.json") %>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function loadSchemas() {
|
|||||||
require('./schemas/objects/memo.json'),
|
require('./schemas/objects/memo.json'),
|
||||||
require('./schemas/objects/memos.json'),
|
require('./schemas/objects/memos.json'),
|
||||||
require('./schemas/objects/public-key.json'),
|
require('./schemas/objects/public-key.json'),
|
||||||
|
require('./schemas/objects/private-key.json'),
|
||||||
require('./schemas/objects/uint32.json'),
|
require('./schemas/objects/uint32.json'),
|
||||||
require('./schemas/objects/value.json'),
|
require('./schemas/objects/value.json'),
|
||||||
require('./schemas/objects/source-adjustment.json'),
|
require('./schemas/objects/source-adjustment.json'),
|
||||||
|
|||||||
@@ -10,7 +10,23 @@
|
|||||||
"secret": {
|
"secret": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "secret",
|
"format": "secret",
|
||||||
"description": "The secret of the account that is initiating the transaction."
|
"description": "The secret of the account that is initiating the transaction. (This field is exclusive with keypair)."
|
||||||
|
},
|
||||||
|
"keypair": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"privateKey": {
|
||||||
|
"type": "privateKey",
|
||||||
|
"description": "The uppercase hexadecimal representation of the secp256k1 or Ed25519 private key."
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"type": "publicKey",
|
||||||
|
"description": "The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).",
|
||||||
|
"required": ["privateKey", "publicKey"],
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -25,5 +41,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["txJSON", "secret"]
|
"required": ["txJSON"],
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"required": ["secret"],
|
||||||
|
"not": {"required": ["keypair"]}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": ["keypair"],
|
||||||
|
"not": {"required": ["secret"]}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/common/schemas/objects/private-key.json
Normal file
7
src/common/schemas/objects/private-key.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "privateKey",
|
||||||
|
"description": "The hexadecimal representation of a secp256k1 or Ed25519 private key.",
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[A-F0-9]+$"
|
||||||
|
}
|
||||||
@@ -2,27 +2,32 @@ import * as utils from './utils'
|
|||||||
import keypairs = require('ripple-keypairs')
|
import keypairs = require('ripple-keypairs')
|
||||||
import binary = require('ripple-binary-codec')
|
import binary = require('ripple-binary-codec')
|
||||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||||
|
import {SignOptions, KeyPair} from './types'
|
||||||
const validate = utils.common.validate
|
const validate = utils.common.validate
|
||||||
|
|
||||||
function computeSignature(tx: Object, privateKey: string, signAs?: string) {
|
function computeSignature(tx: Object, privateKey: string, signAs?: string) {
|
||||||
const signingData = signAs ?
|
const signingData = signAs
|
||||||
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx)
|
? binary.encodeForMultisigning(tx, signAs)
|
||||||
|
: binary.encodeForSigning(tx)
|
||||||
return keypairs.sign(signingData, privateKey)
|
return keypairs.sign(signingData, privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sign(txJSON: string, secret: string, options: {signAs?: string} = {}
|
function signWithKeypair(
|
||||||
): {signedTransaction: string; id: string} {
|
txJSON: string,
|
||||||
validate.sign({txJSON, secret})
|
keypair: KeyPair,
|
||||||
// we can't validate that the secret matches the account because
|
options: SignOptions = {
|
||||||
// the secret could correspond to the regular key
|
signAs: ''
|
||||||
|
}
|
||||||
|
): { signedTransaction: string; id: string } {
|
||||||
|
validate.sign({txJSON, keypair})
|
||||||
|
|
||||||
const tx = JSON.parse(txJSON)
|
const tx = JSON.parse(txJSON)
|
||||||
if (tx.TxnSignature || tx.Signers) {
|
if (tx.TxnSignature || tx.Signers) {
|
||||||
throw new utils.common.errors.ValidationError(
|
throw new utils.common.errors.ValidationError(
|
||||||
'txJSON must not contain "TxnSignature" or "Signers" properties')
|
'txJSON must not contain "TxnSignature" or "Signers" properties'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const keypair = keypairs.deriveKeypair(secret)
|
|
||||||
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey
|
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey
|
||||||
|
|
||||||
if (options.signAs) {
|
if (options.signAs) {
|
||||||
@@ -43,4 +48,20 @@ function sign(txJSON: string, secret: string, options: {signAs?: string} = {}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sign(
|
||||||
|
txJSON: string,
|
||||||
|
secret?: any,
|
||||||
|
options?: SignOptions,
|
||||||
|
keypair?: KeyPair
|
||||||
|
): { signedTransaction: string; id: string } {
|
||||||
|
if (typeof secret === 'string') {
|
||||||
|
// we can't validate that the secret matches the account because
|
||||||
|
// the secret could correspond to the regular key
|
||||||
|
validate.sign({txJSON, secret})
|
||||||
|
return signWithKeypair(txJSON, keypairs.deriveKeypair(secret), options)
|
||||||
|
} else {
|
||||||
|
return signWithKeypair(txJSON, keypair ? keypair : secret, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default sign
|
export default sign
|
||||||
|
|||||||
@@ -50,6 +50,15 @@ export interface OfferCreateTransaction {
|
|||||||
Memos: {Memo: ApiMemo}[]
|
Memos: {Memo: ApiMemo}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type KeyPair = {
|
||||||
|
publicKey: string,
|
||||||
|
privateKey: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SignOptions = {
|
||||||
|
signAs: string
|
||||||
|
}
|
||||||
|
|
||||||
export type Outcome = {
|
export type Outcome = {
|
||||||
result: string,
|
result: string,
|
||||||
ledgerVersion: number,
|
ledgerVersion: number,
|
||||||
|
|||||||
@@ -503,6 +503,58 @@ describe('RippleAPI', function () {
|
|||||||
assert.deepEqual(signature, responses.sign.signAs);
|
assert.deepEqual(signature, responses.sign.signAs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sign - withKeypair', function () {
|
||||||
|
const keypair = {
|
||||||
|
privateKey:
|
||||||
|
'00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
||||||
|
publicKey:
|
||||||
|
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
|
||||||
|
};
|
||||||
|
const result = this.api.sign(requests.sign.normal.txJSON, keypair);
|
||||||
|
assert.deepEqual(result, responses.sign.normal);
|
||||||
|
schemaValidator.schemaValidate('sign', result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sign - withKeypair already signed', function () {
|
||||||
|
const keypair = {
|
||||||
|
privateKey:
|
||||||
|
'00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
||||||
|
publicKey:
|
||||||
|
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
|
||||||
|
};
|
||||||
|
const result = this.api.sign(requests.sign.normal.txJSON, keypair);
|
||||||
|
assert.throws(() => {
|
||||||
|
const tx = JSON.stringify(binary.decode(result.signedTransaction));
|
||||||
|
this.api.sign(tx, keypair);
|
||||||
|
}, /txJSON must not contain "TxnSignature" or "Signers" properties/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sign - withKeypair EscrowExecution', function () {
|
||||||
|
const keypair = {
|
||||||
|
privateKey:
|
||||||
|
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
|
||||||
|
publicKey:
|
||||||
|
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020'
|
||||||
|
};
|
||||||
|
const result = this.api.sign(requests.sign.escrow.txJSON, keypair);
|
||||||
|
assert.deepEqual(result, responses.sign.escrow);
|
||||||
|
schemaValidator.schemaValidate('sign', result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sign - withKeypair signAs', function () {
|
||||||
|
const txJSON = requests.sign.signAs;
|
||||||
|
const keypair = {
|
||||||
|
privateKey:
|
||||||
|
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
|
||||||
|
publicKey:
|
||||||
|
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020'
|
||||||
|
};
|
||||||
|
const signature = this.api.sign(JSON.stringify(txJSON), keypair, {
|
||||||
|
signAs: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
||||||
|
});
|
||||||
|
assert.deepEqual(signature, responses.sign.signAs);
|
||||||
|
});
|
||||||
|
|
||||||
it('submit', function () {
|
it('submit', function () {
|
||||||
return this.api.submit(responses.sign.normal.signedTransaction).then(
|
return this.api.submit(responses.sign.normal.signedTransaction).then(
|
||||||
_.partial(checkResult, responses.submit, 'submit'));
|
_.partial(checkResult, responses.submit, 'submit'));
|
||||||
|
|||||||
@@ -167,6 +167,15 @@ describe('http server integration tests', function() {
|
|||||||
result => assert.deepEqual(result.result, apiResponses.sign.normal)
|
result => assert.deepEqual(result.result, apiResponses.sign.normal)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
createTest(
|
||||||
|
'sign',
|
||||||
|
[{txJSON: apiRequests.sign.normal.txJSON},
|
||||||
|
{keypair: {
|
||||||
|
privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
||||||
|
publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' }}],
|
||||||
|
result => assert.deepEqual(result.result, apiResponses.sign.normal)
|
||||||
|
);
|
||||||
|
|
||||||
createTest(
|
createTest(
|
||||||
'generateAddress',
|
'generateAddress',
|
||||||
[{options: {entropy: random()}}],
|
[{options: {entropy: random()}}],
|
||||||
|
|||||||
Reference in New Issue
Block a user