Skip to content

Commit

Permalink
Add signWithKeypair (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
amougel authored and intelliot committed Apr 3, 2018
1 parent c71540e commit 2570e2a
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 16 deletions.
13 changes: 10 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3847,7 +3847,10 @@ return api.prepareCheckCash(address, checkCash).then(prepared =>

## sign

`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
```
sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}

This comment has been minimized.

Copy link
@sublimator

sublimator Apr 4, 2018

Contributor

Can't see a signWithKeypair :0

This comment has been minimized.

Copy link
@sublimator

sublimator Apr 4, 2018

Contributor

Oh, it's hidden behind this

This comment has been minimized.

Copy link
@intelliot

intelliot Apr 4, 2018

Collaborator

Good point - I’ll clarify this in the changelog (HISTORY.md) later.

sign(txJSON: string, keypair: Object, options: Object): {signedTransaction: string, id: string}
```

Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).

Expand All @@ -3856,9 +3859,12 @@ Sign a prepared transaction. The signed transaction must subsequently be [submit
Name | Type | Description
---- | ---- | -----------
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.* 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

Expand All @@ -3874,7 +3880,8 @@ id | [id](#transaction-id) | The [Transaction ID](#transaction-id) of the signed
```javascript
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
return api.sign(txJSON, secret);
const keypair = { privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A', publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' };
return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
```


Expand Down
8 changes: 6 additions & 2 deletions docs/src/sign.md.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## 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).

Expand All @@ -19,7 +22,8 @@ This method returns an object with the following structure:
```javascript
const txJSON = '{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"12","Sequence":23}';
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") %>
1 change: 1 addition & 0 deletions src/common/schema-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function loadSchemas() {
require('./schemas/objects/memo.json'),
require('./schemas/objects/memos.json'),
require('./schemas/objects/public-key.json'),
require('./schemas/objects/private-key.json'),
require('./schemas/objects/uint32.json'),
require('./schemas/objects/value.json'),
require('./schemas/objects/source-adjustment.json'),
Expand Down
30 changes: 28 additions & 2 deletions src/common/schemas/input/sign.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,23 @@
"secret": {
"type": "string",
"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": {
"type": "object",
Expand All @@ -25,5 +41,15 @@
}
},
"additionalProperties": false,
"required": ["txJSON", "secret"]
"required": ["txJSON"],
"oneOf": [
{
"required": ["secret"],
"not": {"required": ["keypair"]}
},
{
"required": ["keypair"],
"not": {"required": ["secret"]}
}
]
}
7 changes: 7 additions & 0 deletions src/common/schemas/objects/private-key.json
Original file line number Diff line number Diff line change
@@ -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]+$"
}
39 changes: 30 additions & 9 deletions src/transaction/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,32 @@ import * as utils from './utils'
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import {computeBinaryTransactionHash} from 'ripple-hashes'
import {SignOptions, KeyPair} from './types'
const validate = utils.common.validate

function computeSignature(tx: Object, privateKey: string, signAs?: string) {
const signingData = signAs ?
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx)
const signingData = signAs
? binary.encodeForMultisigning(tx, signAs)
: binary.encodeForSigning(tx)
return keypairs.sign(signingData, privateKey)
}

function sign(txJSON: string, secret: string, options: {signAs?: string} = {}
): {signedTransaction: string; id: string} {
validate.sign({txJSON, secret})
// we can't validate that the secret matches the account because
// the secret could correspond to the regular key
function signWithKeypair(
txJSON: string,
keypair: KeyPair,
options: SignOptions = {
signAs: ''
}
): { signedTransaction: string; id: string } {
validate.sign({txJSON, keypair})

const tx = JSON.parse(txJSON)
if (tx.TxnSignature || tx.Signers) {
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

if (options.signAs) {
Expand All @@ -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
9 changes: 9 additions & 0 deletions src/transaction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export interface OfferCreateTransaction {
Memos: {Memo: ApiMemo}[]
}

export type KeyPair = {
publicKey: string,
privateKey: string
}

export type SignOptions = {
signAs: string
}

export type Outcome = {
result: string,
ledgerVersion: number,
Expand Down
52 changes: 52 additions & 0 deletions test/api-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,58 @@ describe('RippleAPI', function () {
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 () {
return this.api.submit(responses.sign.normal.signedTransaction).then(
_.partial(checkResult, responses.submit, 'submit'));
Expand Down
9 changes: 9 additions & 0 deletions test/integration/http-integration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ describe('http server integration tests', function() {
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(
'generateAddress',
[{options: {entropy: random()}}],
Expand Down

0 comments on commit 2570e2a

Please sign in to comment.